Use parsed Claude models and remove auto defaults
This commit is contained in:
@@ -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", () => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user