726 lines
23 KiB
TypeScript
726 lines
23 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import {
|
|
AGENT_PROVIDER_DEFINITIONS,
|
|
type AgentProviderDefinition,
|
|
} from "@server/server/agent/provider-manifest";
|
|
import type {
|
|
AgentMode,
|
|
AgentModelDefinition,
|
|
AgentProvider,
|
|
} from "@server/server/agent/agent-sdk-types";
|
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
|
import { useFormPreferences, type FormPreferences } from "./use-form-preferences";
|
|
|
|
// Explicit overrides from URL params or "New Agent" button
|
|
export interface FormInitialValues {
|
|
serverId?: string | null;
|
|
provider?: AgentProvider;
|
|
modeId?: string | null;
|
|
model?: string | null;
|
|
thinkingOptionId?: string | null;
|
|
workingDir?: string;
|
|
}
|
|
|
|
// Tracks which fields the user has explicitly modified in this session
|
|
interface UserModifiedFields {
|
|
serverId: boolean;
|
|
provider: boolean;
|
|
modeId: boolean;
|
|
model: boolean;
|
|
thinkingOptionId: boolean;
|
|
workingDir: boolean;
|
|
}
|
|
|
|
const INITIAL_USER_MODIFIED: UserModifiedFields = {
|
|
serverId: false,
|
|
provider: false,
|
|
modeId: false,
|
|
model: false,
|
|
thinkingOptionId: false,
|
|
workingDir: false,
|
|
};
|
|
|
|
// Internal form state
|
|
interface FormState {
|
|
serverId: string | null;
|
|
provider: AgentProvider;
|
|
modeId: string;
|
|
model: string;
|
|
thinkingOptionId: string;
|
|
workingDir: string;
|
|
}
|
|
|
|
type UseAgentFormStateOptions = {
|
|
initialServerId?: string | null;
|
|
initialValues?: FormInitialValues;
|
|
isVisible?: boolean;
|
|
isCreateFlow?: boolean;
|
|
isTargetDaemonReady?: boolean;
|
|
onlineServerIds?: string[];
|
|
};
|
|
|
|
type UseAgentFormStateResult = {
|
|
selectedServerId: string | null;
|
|
setSelectedServerId: (value: string | null) => void;
|
|
setSelectedServerIdFromUser: (value: string | null) => void;
|
|
selectedProvider: AgentProvider;
|
|
setProviderFromUser: (provider: AgentProvider) => void;
|
|
selectedMode: string;
|
|
setModeFromUser: (modeId: string) => void;
|
|
selectedModel: string;
|
|
setModelFromUser: (modelId: string) => void;
|
|
selectedThinkingOptionId: string;
|
|
setThinkingOptionFromUser: (thinkingOptionId: string) => void;
|
|
workingDir: string;
|
|
setWorkingDir: (value: string) => void;
|
|
setWorkingDirFromUser: (value: string) => void;
|
|
providerDefinitions: AgentProviderDefinition[];
|
|
providerDefinitionMap: Map<AgentProvider, AgentProviderDefinition>;
|
|
agentDefinition?: AgentProviderDefinition;
|
|
modeOptions: AgentMode[];
|
|
availableModels: AgentModelDefinition[];
|
|
availableThinkingOptions: NonNullable<AgentModelDefinition["thinkingOptions"]>;
|
|
isModelLoading: boolean;
|
|
modelError: string | null;
|
|
refreshProviderModels: () => void;
|
|
workingDirIsEmpty: boolean;
|
|
persistFormPreferences: () => Promise<void>;
|
|
};
|
|
|
|
const allProviderDefinitions = AGENT_PROVIDER_DEFINITIONS;
|
|
const allProviderDefinitionMap = new Map<AgentProvider, AgentProviderDefinition>(
|
|
allProviderDefinitions.map((definition) => [definition.id, definition])
|
|
);
|
|
const fallbackDefinition = allProviderDefinitions[0];
|
|
const DEFAULT_PROVIDER: AgentProvider = fallbackDefinition?.id ?? "claude";
|
|
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") {
|
|
return "";
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
function resolveDefaultModel(
|
|
availableModels: AgentModelDefinition[] | null
|
|
): AgentModelDefinition | null {
|
|
if (!availableModels || availableModels.length === 0) {
|
|
return null;
|
|
}
|
|
return availableModels.find((model) => model.isDefault) ?? availableModels[0] ?? null;
|
|
}
|
|
|
|
function resolveEffectiveModel(
|
|
availableModels: AgentModelDefinition[] | null,
|
|
modelId: string
|
|
): AgentModelDefinition | null {
|
|
if (!availableModels || availableModels.length === 0) {
|
|
return null;
|
|
}
|
|
const normalizedModelId = modelId.trim();
|
|
if (!normalizedModelId) {
|
|
return resolveDefaultModel(availableModels);
|
|
}
|
|
return (
|
|
availableModels.find((model) => model.id === normalizedModelId) ??
|
|
resolveDefaultModel(availableModels)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Pure function that resolves form state from multiple data sources.
|
|
* Priority: explicit (URL params) > preferences > provider defaults > fallback
|
|
*
|
|
* Only resolves fields that haven't been user-modified.
|
|
*/
|
|
function resolveFormState(
|
|
initialValues: FormInitialValues | undefined,
|
|
preferences: FormPreferences | null,
|
|
availableModels: AgentModelDefinition[] | null,
|
|
userModified: UserModifiedFields,
|
|
currentState: FormState,
|
|
validServerIds: Set<string>,
|
|
allowedProviderMap: Map<AgentProvider, AgentProviderDefinition> = allProviderDefinitionMap
|
|
): FormState {
|
|
// Start with current state - we only update non-user-modified fields
|
|
const result = { ...currentState };
|
|
const fallbackProvider = allowedProviderMap.keys().next().value as
|
|
| AgentProvider
|
|
| undefined;
|
|
|
|
// 1. Resolve provider first (other fields depend on it)
|
|
if (!userModified.provider) {
|
|
if (initialValues?.provider && allowedProviderMap.has(initialValues.provider)) {
|
|
result.provider = initialValues.provider;
|
|
} else if (
|
|
preferences?.provider &&
|
|
allowedProviderMap.has(preferences.provider as AgentProvider)
|
|
) {
|
|
result.provider = preferences.provider as AgentProvider;
|
|
} else if (!allowedProviderMap.has(result.provider) && fallbackProvider) {
|
|
result.provider = fallbackProvider;
|
|
}
|
|
// else keep current (initialized to DEFAULT_PROVIDER)
|
|
} else if (!allowedProviderMap.has(result.provider) && fallbackProvider) {
|
|
result.provider = fallbackProvider;
|
|
}
|
|
|
|
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) ?? [];
|
|
|
|
if (
|
|
typeof initialValues?.modeId === "string" &&
|
|
initialValues.modeId.length > 0 &&
|
|
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] ?? "";
|
|
}
|
|
}
|
|
|
|
// 3. Resolve model (depends on provider + availableModels)
|
|
if (!userModified.model) {
|
|
const isValidModel = (m: string) =>
|
|
availableModels?.some((am) => am.id === m) ?? false;
|
|
const initialModel = normalizeSelectedModelId(initialValues?.model);
|
|
const preferredModel = normalizeSelectedModelId(providerPrefs?.model);
|
|
|
|
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 = "";
|
|
}
|
|
} else {
|
|
result.model = "";
|
|
}
|
|
}
|
|
|
|
// 4. Resolve thinking option (depends on effective model)
|
|
const initialThinkingOptionId =
|
|
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 = "";
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 5. Resolve serverId (independent)
|
|
// Only use stored serverId if the host still exists in the registry
|
|
if (!userModified.serverId) {
|
|
if (initialValues?.serverId !== undefined) {
|
|
result.serverId = initialValues.serverId;
|
|
} else if (preferences?.serverId && validServerIds.has(preferences.serverId)) {
|
|
result.serverId = preferences.serverId;
|
|
}
|
|
// else keep current
|
|
}
|
|
|
|
// 6. Resolve workingDir (independent)
|
|
if (!userModified.workingDir) {
|
|
if (initialValues?.workingDir !== undefined) {
|
|
result.workingDir = initialValues.workingDir;
|
|
} else if (preferences?.workingDir) {
|
|
result.workingDir = preferences.workingDir;
|
|
}
|
|
// else keep current (empty string)
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function combineInitialValues(
|
|
initialValues: FormInitialValues | undefined,
|
|
initialServerId: string | null
|
|
): FormInitialValues | undefined {
|
|
const hasExplicitServerId = initialValues?.serverId !== undefined;
|
|
const serverIdFromOptions = initialServerId === null ? undefined : initialServerId;
|
|
|
|
// If nobody provided initial values or an explicit serverId, let preferences drive defaults.
|
|
if (!initialValues && !hasExplicitServerId && serverIdFromOptions === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
if (hasExplicitServerId) {
|
|
return { ...initialValues, serverId: initialValues?.serverId };
|
|
}
|
|
|
|
if (serverIdFromOptions !== undefined) {
|
|
return { ...initialValues, serverId: serverIdFromOptions };
|
|
}
|
|
|
|
return initialValues;
|
|
}
|
|
|
|
export function useAgentFormState(
|
|
options: UseAgentFormStateOptions = {}
|
|
): UseAgentFormStateResult {
|
|
const {
|
|
initialServerId = null,
|
|
initialValues,
|
|
isVisible = true,
|
|
isCreateFlow = true,
|
|
isTargetDaemonReady = true,
|
|
onlineServerIds = [],
|
|
} = options;
|
|
|
|
const {
|
|
preferences,
|
|
isLoading: isPreferencesLoading,
|
|
updatePreferences,
|
|
updateProviderPreferences,
|
|
} = useFormPreferences();
|
|
|
|
const { daemons } = useDaemonRegistry();
|
|
|
|
// Build a set of valid server IDs for preference validation
|
|
const validServerIds = useMemo(
|
|
() => new Set(daemons.map((d) => d.serverId)),
|
|
[daemons]
|
|
);
|
|
|
|
// Track which fields the user has explicitly modified
|
|
const [userModified, setUserModified] = useState<UserModifiedFields>(INITIAL_USER_MODIFIED);
|
|
|
|
// Form state
|
|
const [formState, setFormState] = useState<FormState>(() => ({
|
|
serverId: initialServerId,
|
|
provider: DEFAULT_PROVIDER,
|
|
modeId: DEFAULT_MODE_FOR_DEFAULT_PROVIDER,
|
|
model: "",
|
|
thinkingOptionId: "",
|
|
workingDir: "",
|
|
}));
|
|
const formStateRef = useRef(formState);
|
|
useEffect(() => {
|
|
formStateRef.current = formState;
|
|
}, [formState]);
|
|
|
|
// Track if we've done initial resolution (to avoid flickering)
|
|
const hasResolvedRef = useRef(false);
|
|
|
|
// Reset user modifications when form becomes invisible
|
|
useEffect(() => {
|
|
if (!isVisible) {
|
|
setUserModified(INITIAL_USER_MODIFIED);
|
|
hasResolvedRef.current = false;
|
|
}
|
|
}, [isVisible]);
|
|
|
|
// Session state for provider model listing
|
|
const { client, isConnected } = useHostRuntimeSession(formState.serverId ?? "");
|
|
|
|
const availableProvidersQuery = useQuery({
|
|
queryKey: ["availableProviders", formState.serverId],
|
|
enabled: Boolean(
|
|
isVisible && isTargetDaemonReady && formState.serverId && client && isConnected
|
|
),
|
|
staleTime: 60 * 1000,
|
|
queryFn: async () => {
|
|
if (!client) {
|
|
throw new Error("Host is not connected");
|
|
}
|
|
const payload = await client.listAvailableProviders();
|
|
if (payload.error) {
|
|
throw new Error(payload.error);
|
|
}
|
|
return payload.providers
|
|
.filter((entry) => entry.available)
|
|
.map((entry) => entry.provider);
|
|
},
|
|
});
|
|
|
|
const providerDefinitions = useMemo(() => {
|
|
const availableProviders = availableProvidersQuery.data;
|
|
if (!availableProviders) {
|
|
return [];
|
|
}
|
|
const available = new Set(availableProviders);
|
|
return allProviderDefinitions.filter((definition) =>
|
|
available.has(definition.id as AgentProvider)
|
|
);
|
|
}, [availableProvidersQuery.data]);
|
|
|
|
const providerDefinitionMap = useMemo(
|
|
() =>
|
|
new Map<AgentProvider, AgentProviderDefinition>(
|
|
providerDefinitions.map((definition) => [definition.id as AgentProvider, definition])
|
|
),
|
|
[providerDefinitions]
|
|
);
|
|
|
|
const [debouncedCwd, setDebouncedCwd] = useState<string | undefined>(undefined);
|
|
useEffect(() => {
|
|
const trimmed = formState.workingDir.trim();
|
|
const next = trimmed.length > 0 ? trimmed : undefined;
|
|
const timer = setTimeout(() => setDebouncedCwd(next), 180);
|
|
return () => clearTimeout(timer);
|
|
}, [formState.workingDir]);
|
|
|
|
const providerModelsQuery = useQuery({
|
|
queryKey: ["providerModels", formState.serverId, formState.provider, debouncedCwd],
|
|
enabled: Boolean(
|
|
isVisible &&
|
|
isTargetDaemonReady &&
|
|
formState.serverId &&
|
|
client &&
|
|
isConnected &&
|
|
providerDefinitionMap.has(formState.provider)
|
|
),
|
|
staleTime: 5 * 60 * 1000,
|
|
queryFn: async () => {
|
|
if (!client) {
|
|
throw new Error("Host is not connected");
|
|
}
|
|
const payload = await client.listProviderModels(formState.provider, {
|
|
cwd: debouncedCwd,
|
|
});
|
|
if (payload.error) {
|
|
throw new Error(payload.error);
|
|
}
|
|
return payload.models ?? [];
|
|
},
|
|
});
|
|
|
|
const availableModels = providerModelsQuery.data ?? null;
|
|
|
|
// Combine initialValues with initialServerId for resolution
|
|
const combinedInitialValues = useMemo((): FormInitialValues | undefined => {
|
|
return combineInitialValues(initialValues, initialServerId);
|
|
}, [initialValues, initialServerId]);
|
|
|
|
// Resolve form state when data sources change
|
|
useEffect(() => {
|
|
if (!isVisible || !isCreateFlow) {
|
|
return;
|
|
}
|
|
|
|
// Wait for preferences to load before first resolution, unless explicit URL overrides exist.
|
|
if (isPreferencesLoading && !hasResolvedRef.current && !combinedInitialValues) {
|
|
return;
|
|
}
|
|
|
|
const resolved = resolveFormState(
|
|
combinedInitialValues,
|
|
preferences,
|
|
availableModels,
|
|
userModified,
|
|
formStateRef.current,
|
|
validServerIds,
|
|
providerDefinitionMap
|
|
);
|
|
|
|
// Only update if something changed
|
|
if (
|
|
resolved.serverId !== formStateRef.current.serverId ||
|
|
resolved.provider !== formStateRef.current.provider ||
|
|
resolved.modeId !== formStateRef.current.modeId ||
|
|
resolved.model !== formStateRef.current.model ||
|
|
resolved.thinkingOptionId !== formStateRef.current.thinkingOptionId ||
|
|
resolved.workingDir !== formStateRef.current.workingDir
|
|
) {
|
|
setFormState(resolved);
|
|
}
|
|
|
|
hasResolvedRef.current = true;
|
|
}, [
|
|
isVisible,
|
|
isCreateFlow,
|
|
isPreferencesLoading,
|
|
combinedInitialValues,
|
|
preferences,
|
|
availableModels,
|
|
userModified,
|
|
validServerIds,
|
|
providerDefinitionMap,
|
|
]);
|
|
|
|
// Auto-select the first online host when:
|
|
// - no URL override
|
|
// - no stored preference applied
|
|
// - user hasn't manually picked a host in this session
|
|
useEffect(() => {
|
|
if (!isVisible || !isCreateFlow) return;
|
|
if (isPreferencesLoading) return;
|
|
if (!hasResolvedRef.current) return;
|
|
if (userModified.serverId) return;
|
|
if (combinedInitialValues?.serverId !== undefined) return;
|
|
if (formStateRef.current.serverId) return;
|
|
|
|
const candidate = onlineServerIds.find((id) => validServerIds.has(id)) ?? null;
|
|
if (!candidate) return;
|
|
|
|
setFormState((prev) => (prev.serverId ? prev : { ...prev, serverId: candidate }));
|
|
}, [
|
|
combinedInitialValues?.serverId,
|
|
isCreateFlow,
|
|
isPreferencesLoading,
|
|
isVisible,
|
|
onlineServerIds.join("|"),
|
|
userModified.serverId,
|
|
validServerIds,
|
|
]);
|
|
|
|
// Persist inferred serverId so reloads keep the selection (e.g. URL serverId or first-time load).
|
|
useEffect(() => {
|
|
if (!isVisible || !isCreateFlow) return;
|
|
if (isPreferencesLoading) return;
|
|
if (userModified.serverId) return;
|
|
const serverId = formState.serverId;
|
|
if (!serverId) return;
|
|
if (preferences?.serverId === serverId) return;
|
|
void updatePreferences({ serverId });
|
|
}, [
|
|
isVisible,
|
|
isCreateFlow,
|
|
isPreferencesLoading,
|
|
userModified.serverId,
|
|
formState.serverId,
|
|
preferences?.serverId,
|
|
updatePreferences,
|
|
]);
|
|
|
|
// User setters - mark fields as modified and persist to preferences
|
|
const setSelectedServerIdFromUser = useCallback(
|
|
(value: string | null) => {
|
|
setFormState((prev) => ({ ...prev, serverId: value }));
|
|
setUserModified((prev) => ({ ...prev, serverId: true }));
|
|
void updatePreferences({ serverId: value ?? undefined });
|
|
},
|
|
[updatePreferences]
|
|
);
|
|
|
|
const setProviderFromUser = useCallback(
|
|
(provider: AgentProvider) => {
|
|
setFormState((prev) => ({ ...prev, provider }));
|
|
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 ?? "",
|
|
}));
|
|
},
|
|
[preferences?.providerPreferences, 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 }));
|
|
setUserModified((prev) => ({ ...prev, model: true }));
|
|
void updateProviderPreferences(formState.provider, { model: normalizedModelId });
|
|
},
|
|
[formState.provider, updateProviderPreferences]
|
|
);
|
|
|
|
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) => {
|
|
setFormState((prev) => ({ ...prev, workingDir: value }));
|
|
}, []);
|
|
|
|
const setWorkingDirFromUser = useCallback(
|
|
(value: string) => {
|
|
setFormState((prev) => ({ ...prev, workingDir: value }));
|
|
setUserModified((prev) => ({ ...prev, workingDir: true }));
|
|
void updatePreferences({ workingDir: value });
|
|
},
|
|
[updatePreferences]
|
|
);
|
|
|
|
const setSelectedServerId = useCallback((value: string | null) => {
|
|
setFormState((prev) => ({ ...prev, serverId: value }));
|
|
}, []);
|
|
|
|
const refreshProviderModels = useCallback(() => {
|
|
void providerModelsQuery.refetch();
|
|
}, [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);
|
|
const modeOptions = agentDefinition?.modes ?? [];
|
|
const effectiveModel = resolveEffectiveModel(availableModels, formState.model);
|
|
const availableThinkingOptions = effectiveModel?.thinkingOptions ?? [];
|
|
const isModelLoading = providerModelsQuery.isLoading || providerModelsQuery.isFetching;
|
|
const modelError =
|
|
providerModelsQuery.error instanceof Error ? providerModelsQuery.error.message : null;
|
|
|
|
const workingDirIsEmpty = !formState.workingDir.trim();
|
|
|
|
return useMemo(
|
|
() => ({
|
|
selectedServerId: formState.serverId,
|
|
setSelectedServerId,
|
|
setSelectedServerIdFromUser,
|
|
selectedProvider: formState.provider,
|
|
setProviderFromUser,
|
|
selectedMode: formState.modeId,
|
|
setModeFromUser,
|
|
selectedModel: formState.model,
|
|
setModelFromUser,
|
|
selectedThinkingOptionId: formState.thinkingOptionId,
|
|
setThinkingOptionFromUser,
|
|
workingDir: formState.workingDir,
|
|
setWorkingDir,
|
|
setWorkingDirFromUser,
|
|
providerDefinitions,
|
|
providerDefinitionMap,
|
|
agentDefinition,
|
|
modeOptions,
|
|
availableModels: availableModels ?? [],
|
|
availableThinkingOptions,
|
|
isModelLoading,
|
|
modelError,
|
|
refreshProviderModels,
|
|
workingDirIsEmpty,
|
|
persistFormPreferences,
|
|
}),
|
|
[
|
|
formState.serverId,
|
|
formState.provider,
|
|
formState.modeId,
|
|
formState.model,
|
|
formState.thinkingOptionId,
|
|
formState.workingDir,
|
|
setSelectedServerId,
|
|
setSelectedServerIdFromUser,
|
|
setProviderFromUser,
|
|
setModeFromUser,
|
|
setModelFromUser,
|
|
setThinkingOptionFromUser,
|
|
setWorkingDir,
|
|
setWorkingDirFromUser,
|
|
providerDefinitions,
|
|
providerDefinitionMap,
|
|
agentDefinition,
|
|
modeOptions,
|
|
availableModels,
|
|
availableThinkingOptions,
|
|
isModelLoading,
|
|
modelError,
|
|
refreshProviderModels,
|
|
workingDirIsEmpty,
|
|
persistFormPreferences,
|
|
]
|
|
);
|
|
}
|
|
|
|
// Re-export for backwards compatibility
|
|
export type CreateAgentInitialValues = FormInitialValues;
|
|
|
|
export const __private__ = {
|
|
combineInitialValues,
|
|
resolveFormState,
|
|
};
|