Use parsed Claude models and remove auto defaults

This commit is contained in:
Mohamed Boudra
2026-03-24 23:32:23 +07:00
parent 891dbb0f6e
commit fadeca82eb
20 changed files with 750 additions and 568 deletions
@@ -80,20 +80,14 @@ describe("useAgentFormState", () => {
new Set<string>(),
);
expect(resolved.model).toBe("gpt-5.3-codex");
expect(resolved.thinkingOptionId).toBe("xhigh");
});
it("keeps provider thinking preference when it is valid for the effective model", () => {
it("prefers provider defaults on fresh drafts", () => {
const resolved = __private__.resolveFormState(
undefined,
{
provider: "codex",
providerPreferences: {
codex: {
thinkingOptionId: "low",
},
},
},
{ provider: "codex" },
codexModels,
{
serverId: false,
@@ -114,20 +108,14 @@ describe("useAgentFormState", () => {
new Set<string>(),
);
expect(resolved.thinkingOptionId).toBe("low");
expect(resolved.model).toBe("gpt-5.3-codex");
expect(resolved.thinkingOptionId).toBe("xhigh");
});
it("falls back to model default when saved thinking preference is invalid", () => {
const resolved = __private__.resolveFormState(
undefined,
{
provider: "codex",
providerPreferences: {
codex: {
thinkingOptionId: "medium",
},
},
},
{ provider: "codex" },
codexModels,
{
serverId: false,
@@ -151,7 +139,7 @@ describe("useAgentFormState", () => {
expect(resolved.thinkingOptionId).toBe("xhigh");
});
it("normalizes legacy model id 'default' from initial values to auto", () => {
it("normalizes legacy model id 'default' from initial values to the provider default model", () => {
const resolved = __private__.resolveFormState(
{ model: "default" },
{ provider: "codex" },
@@ -175,20 +163,13 @@ describe("useAgentFormState", () => {
new Set<string>(),
);
expect(resolved.model).toBe("");
expect(resolved.model).toBe("gpt-5.3-codex");
});
it("normalizes legacy model id 'default' from provider preferences to auto", () => {
it("normalizes legacy model id 'default' to the provider default model", () => {
const resolved = __private__.resolveFormState(
undefined,
{
provider: "codex",
providerPreferences: {
codex: {
model: "default",
},
},
},
{ model: "default" },
{ provider: "codex" },
codexModels,
{
serverId: false,
@@ -209,7 +190,76 @@ describe("useAgentFormState", () => {
new Set<string>(),
);
expect(resolved.model).toBe("");
expect(resolved.model).toBe("gpt-5.3-codex");
});
it("keeps an explicit initial thinking option when it is valid", () => {
const resolved = __private__.resolveFormState(
{ thinkingOptionId: "low" },
{ provider: "codex" },
codexModels,
{
serverId: false,
provider: false,
modeId: false,
model: false,
thinkingOptionId: false,
workingDir: false,
},
{
serverId: null,
provider: "codex",
modeId: "",
model: "",
thinkingOptionId: "",
workingDir: "",
},
new Set<string>(),
);
expect(resolved.model).toBe("gpt-5.3-codex");
expect(resolved.thinkingOptionId).toBe("low");
});
it("leaves thinking unset when the model exposes options without a provider default", () => {
const claudeModels: AgentModelDefinition[] = [
{
provider: "claude",
id: "default",
label: "Default (Sonnet 4.6)",
isDefault: true,
thinkingOptions: [
{ id: "low", label: "Low" },
{ id: "medium", label: "Medium" },
],
},
];
const resolved = __private__.resolveFormState(
undefined,
{ provider: "claude" },
claudeModels,
{
serverId: false,
provider: false,
modeId: false,
model: false,
thinkingOptionId: false,
workingDir: false,
},
{
serverId: null,
provider: "claude",
modeId: "",
model: "",
thinkingOptionId: "",
workingDir: "",
},
new Set<string>(),
);
expect(resolved.model).toBe("default");
expect(resolved.thinkingOptionId).toBe("");
});
it("resolves provider only from allowed provider map", () => {
+80 -80
View File
@@ -102,7 +102,7 @@ const DEFAULT_MODE_FOR_DEFAULT_PROVIDER = fallbackDefinition?.defaultModeId ?? "
function normalizeSelectedModelId(modelId: string | null | undefined): string {
const normalized = typeof modelId === "string" ? modelId.trim() : "";
if (!normalized || normalized.toLowerCase() === "default") {
if (!normalized) {
return "";
}
return normalized;
@@ -117,6 +117,10 @@ function resolveDefaultModel(
return availableModels.find((model) => model.isDefault) ?? availableModels[0] ?? null;
}
function resolveDefaultModelId(availableModels: AgentModelDefinition[] | null): string {
return resolveDefaultModel(availableModels)?.id ?? "";
}
function resolveEffectiveModel(
availableModels: AgentModelDefinition[] | null,
modelId: string,
@@ -134,9 +138,31 @@ function resolveEffectiveModel(
);
}
function resolveThinkingOptionId(args: {
availableModels: AgentModelDefinition[] | null;
modelId: string;
requestedThinkingOptionId: string;
}): string {
const effectiveModel = resolveEffectiveModel(args.availableModels, args.modelId);
const thinkingOptions = effectiveModel?.thinkingOptions ?? [];
if (thinkingOptions.length === 0) {
return "";
}
const normalizedThinkingOptionId = args.requestedThinkingOptionId.trim();
if (
normalizedThinkingOptionId &&
thinkingOptions.some((option) => option.id === normalizedThinkingOptionId)
) {
return normalizedThinkingOptionId;
}
return effectiveModel?.defaultThinkingOptionId ?? "";
}
/**
* Pure function that resolves form state from multiple data sources.
* Priority: explicit (URL params) > preferences > provider defaults > fallback
* Priority: explicit (URL params) > provider defaults > lightweight app prefs > fallback
*
* Only resolves fields that haven't been user-modified.
*/
@@ -171,8 +197,6 @@ function resolveFormState(
}
const providerDef = allowedProviderMap.get(result.provider);
const providerPrefs = preferences?.providerPreferences?.[result.provider];
// 2. Resolve modeId (depends on provider)
if (!userModified.modeId) {
const validModeIds = providerDef?.modes.map((m) => m.id) ?? [];
@@ -183,8 +207,6 @@ function resolveFormState(
validModeIds.includes(initialValues.modeId)
) {
result.modeId = initialValues.modeId;
} else if (providerPrefs?.mode && validModeIds.includes(providerPrefs.mode)) {
result.modeId = providerPrefs.mode;
} else {
result.modeId = providerDef?.defaultModeId ?? validModeIds[0] ?? "";
}
@@ -194,26 +216,18 @@ function resolveFormState(
if (!userModified.model) {
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) {
// If models aren't loaded yet, trust the initial value
// It will be validated once models load
if (!availableModels || isValidModel(initialModel)) {
result.model = initialModel;
} else if (preferredModel && isValidModel(preferredModel)) {
result.model = preferredModel;
} else {
result.model = "";
}
} else if (preferredModel) {
// If models haven't loaded yet, optimistically apply the stored preference.
// We'll validate once models load and clear it if it isn't available.
if (!availableModels || isValidModel(preferredModel)) {
result.model = preferredModel;
} else {
result.model = "";
result.model = defaultModelId;
}
} else if (defaultModelId) {
result.model = defaultModelId;
} else {
result.model = "";
}
@@ -224,13 +238,10 @@ function resolveFormState(
typeof initialValues?.thinkingOptionId === "string"
? initialValues.thinkingOptionId.trim()
: "";
const preferredThinkingOptionId = providerPrefs?.thinkingOptionId?.trim() ?? "";
if (!userModified.thinkingOptionId) {
if (initialThinkingOptionId.length > 0) {
result.thinkingOptionId = initialThinkingOptionId;
} else if (preferredThinkingOptionId.length > 0) {
result.thinkingOptionId = preferredThinkingOptionId;
} else {
result.thinkingOptionId = "";
}
@@ -238,18 +249,11 @@ function resolveFormState(
// Validate thinking option once model metadata is available.
if (availableModels) {
const effectiveModel = resolveEffectiveModel(availableModels, result.model);
const thinkingOptions = effectiveModel?.thinkingOptions ?? [];
if (thinkingOptions.length === 0) {
result.thinkingOptionId = "";
} else {
const thinkingIds = new Set(thinkingOptions.map((option) => option.id));
const defaultThinkingOptionId =
effectiveModel?.defaultThinkingOptionId ?? thinkingOptions[0]?.id ?? "";
if (!result.thinkingOptionId || !thinkingIds.has(result.thinkingOptionId)) {
result.thinkingOptionId = defaultThinkingOptionId;
}
}
result.thinkingOptionId = resolveThinkingOptionId({
availableModels,
modelId: result.model,
requestedThinkingOptionId: result.thinkingOptionId,
});
}
// 5. Resolve serverId (independent)
@@ -313,7 +317,6 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
preferences,
isLoading: isPreferencesLoading,
updatePreferences,
updateProviderPreferences,
} = useFormPreferences();
const daemons = useHosts();
@@ -568,76 +571,89 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
const setProviderFromUser = useCallback(
(provider: AgentProvider) => {
setFormState((prev) => ({ ...prev, provider }));
const providerModels = allProviderModels.get(provider) ?? null;
const providerDef = providerDefinitionMap.get(provider);
const defaultModelId = resolveDefaultModelId(providerModels);
const defaultThinkingOptionId = resolveThinkingOptionId({
availableModels: providerModels,
modelId: defaultModelId,
requestedThinkingOptionId: "",
});
setUserModified((prev) => ({ ...prev, provider: true }));
void updatePreferences({ provider });
// When provider changes, reset mode and model to provider defaults
// (unless user has explicitly set them)
const providerDef = providerDefinitionMap.get(provider);
const providerPrefs = preferences?.providerPreferences?.[provider];
setFormState((prev) => ({
...prev,
provider,
modeId: providerPrefs?.mode ?? providerDef?.defaultModeId ?? "",
model: normalizeSelectedModelId(providerPrefs?.model),
thinkingOptionId: providerPrefs?.thinkingOptionId ?? "",
modeId: providerDef?.defaultModeId ?? "",
model: defaultModelId,
thinkingOptionId: defaultThinkingOptionId,
}));
},
[preferences?.providerPreferences, providerDefinitionMap, updatePreferences],
[allProviderModels, providerDefinitionMap, updatePreferences],
);
const setProviderAndModelFromUser = useCallback(
(provider: AgentProvider, modelId: string) => {
const providerDef = providerDefinitionMap.get(provider);
const providerPrefs = preferences?.providerPreferences?.[provider];
const providerModels = allProviderModels.get(provider) ?? null;
const normalizedModelId = normalizeSelectedModelId(modelId);
const nextModelId = normalizedModelId || resolveDefaultModelId(providerModels);
const nextThinkingOptionId = resolveThinkingOptionId({
availableModels: providerModels,
modelId: nextModelId,
requestedThinkingOptionId: "",
});
setFormState((prev) => ({
...prev,
provider,
model: modelId,
modeId: providerPrefs?.mode ?? providerDef?.defaultModeId ?? "",
thinkingOptionId: providerPrefs?.thinkingOptionId ?? "",
model: nextModelId,
modeId: providerDef?.defaultModeId ?? "",
thinkingOptionId: nextThinkingOptionId,
}));
setUserModified((prev) => ({ ...prev, provider: true, model: true }));
void updatePreferences({ provider });
void updateProviderPreferences(provider, { model: modelId });
},
[
preferences?.providerPreferences,
providerDefinitionMap,
updatePreferences,
updateProviderPreferences,
],
[allProviderModels, providerDefinitionMap, updatePreferences],
);
const setModeFromUser = useCallback(
(modeId: string) => {
setFormState((prev) => ({ ...prev, modeId }));
setUserModified((prev) => ({ ...prev, modeId: true }));
void updateProviderPreferences(formState.provider, { mode: modeId });
},
[formState.provider, updateProviderPreferences],
[],
);
const setModelFromUser = useCallback(
(modelId: string) => {
const normalizedModelId = normalizeSelectedModelId(modelId);
setFormState((prev) => ({ ...prev, model: normalizedModelId }));
const nextModelId = normalizedModelId || resolveDefaultModelId(availableModels);
const nextThinkingOptionId = resolveThinkingOptionId({
availableModels,
modelId: nextModelId,
requestedThinkingOptionId: userModified.thinkingOptionId
? formStateRef.current.thinkingOptionId
: "",
});
setFormState((prev) => ({
...prev,
model: nextModelId,
thinkingOptionId: nextThinkingOptionId,
}));
setUserModified((prev) => ({ ...prev, model: true }));
void updateProviderPreferences(formState.provider, { model: normalizedModelId });
},
[formState.provider, updateProviderPreferences],
[availableModels, userModified.thinkingOptionId],
);
const setThinkingOptionFromUser = useCallback(
(thinkingOptionId: string) => {
setFormState((prev) => ({ ...prev, thinkingOptionId }));
setUserModified((prev) => ({ ...prev, thinkingOptionId: true }));
void updateProviderPreferences(formState.provider, { thinkingOptionId });
},
[formState.provider, updateProviderPreferences],
[],
);
const setWorkingDir = useCallback((value: string) => {
@@ -662,34 +678,16 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
}, [providerModelsQuery]);
const persistFormPreferences = useCallback(async () => {
const providerPreferenceUpdates: {
mode: string;
model: string;
thinkingOptionId?: string;
} = {
mode: formState.modeId,
model: formState.model,
};
if (userModified.thinkingOptionId) {
providerPreferenceUpdates.thinkingOptionId = formState.thinkingOptionId;
}
await updatePreferences({
workingDir: formState.workingDir,
provider: formState.provider,
serverId: formState.serverId ?? undefined,
});
await updateProviderPreferences(formState.provider, providerPreferenceUpdates);
}, [
formState.modeId,
formState.model,
formState.provider,
formState.serverId,
formState.thinkingOptionId,
formState.workingDir,
userModified.thinkingOptionId,
updatePreferences,
updateProviderPreferences,
]);
const agentDefinition = providerDefinitionMap.get(formState.provider);
@@ -771,5 +769,7 @@ export type CreateAgentInitialValues = FormInitialValues;
export const __private__ = {
combineInitialValues,
resolveDefaultModel,
resolveFormState,
resolveThinkingOptionId,
};
@@ -2,25 +2,15 @@ import { useCallback } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { z } from "zod";
import type { AgentProvider } from "@server/server/agent/agent-sdk-types";
const FORM_PREFERENCES_STORAGE_KEY = "@paseo:create-agent-preferences";
const FORM_PREFERENCES_QUERY_KEY = ["form-preferences"];
const providerPreferencesSchema = z.object({
model: z.string().optional(),
mode: z.string().optional(),
thinkingOptionId: z.string().optional(),
});
const formPreferencesSchema = z.object({
workingDir: z.string().optional(),
provider: z.string().optional(),
serverId: z.string().optional(),
providerPreferences: z.record(providerPreferencesSchema).optional(),
});
export type ProviderPreferences = z.infer<typeof providerPreferencesSchema>;
export type FormPreferences = z.infer<typeof formPreferencesSchema>;
const DEFAULT_FORM_PREFERENCES: FormPreferences = {};
@@ -35,12 +25,7 @@ async function loadFormPreferences(): Promise<FormPreferences> {
export interface UseFormPreferencesReturn {
preferences: FormPreferences;
isLoading: boolean;
getProviderPreferences: (provider: AgentProvider) => ProviderPreferences | undefined;
updatePreferences: (updates: Partial<FormPreferences>) => Promise<void>;
updateProviderPreferences: (
provider: AgentProvider,
updates: Partial<ProviderPreferences>,
) => Promise<void>;
}
export function useFormPreferences(): UseFormPreferencesReturn {
@@ -54,13 +39,6 @@ export function useFormPreferences(): UseFormPreferencesReturn {
const preferences = data ?? DEFAULT_FORM_PREFERENCES;
const getProviderPreferences = useCallback(
(provider: AgentProvider): ProviderPreferences | undefined => {
return preferences.providerPreferences?.[provider];
},
[preferences.providerPreferences],
);
const updatePreferences = useCallback(
async (updates: Partial<FormPreferences>) => {
const prev =
@@ -73,32 +51,9 @@ export function useFormPreferences(): UseFormPreferencesReturn {
[queryClient],
);
const updateProviderPreferences = useCallback(
async (provider: AgentProvider, updates: Partial<ProviderPreferences>) => {
const prev =
queryClient.getQueryData<FormPreferences>(FORM_PREFERENCES_QUERY_KEY) ??
DEFAULT_FORM_PREFERENCES;
const next: FormPreferences = {
...prev,
providerPreferences: {
...prev.providerPreferences,
[provider]: {
...prev.providerPreferences?.[provider],
...updates,
},
},
};
queryClient.setQueryData<FormPreferences>(FORM_PREFERENCES_QUERY_KEY, next);
await AsyncStorage.setItem(FORM_PREFERENCES_STORAGE_KEY, JSON.stringify(next));
},
[queryClient],
);
return {
preferences,
isLoading: isPending,
getProviderPreferences,
updatePreferences,
updateProviderPreferences,
};
}