Cluster T3.a — mechanical fixes for consistent-return and no-floating-promises. Files touched (15): - server/shared/tool-call-display.ts — default throw in buildCanonicalDetailDisplay switch - server/agent/prompt-attachments.ts — default throw in renderPromptAttachmentAsText switch - server/workspace-directory.ts — default throw in getSortValue switch - server/agent/mcp-server.ts — default throw in mcpCreateWorktreeInput switch - server/terminal/terminal-session-controller.ts — default return in dispatch switch - server/daemon-e2e/agent-configs.ts — default return false in isProviderAvailable switch - server/agent/agent-manager.ts — void dispatchSessionEvent fire-and-forget call - server/agent/mcp-shared.ts — void 4 notify() floating calls - app/agent-status-bar.utils.ts — default throw in getStatusSelectorHint switch - app/keyboard-shortcuts.ts — default throw in resolvePayload switch - app/resolve-agent-form.ts — default throw in resolveAgentForm switch - app/ui/dropdown-menu.tsx — void .then() + return undefined for promise/always-return - app/use-agent-form-state.ts — void refreshSnapshot() floating call - cli/worktree/create.ts — default throw in toDaemonCreateInput switch - cli/onboard.ts — return process.exit(1) to satisfy consistent-return in catch Deferred (outside 15-file cap or in EXCLUDE list): - server/worktree-session.ts — getSortValue (same pattern, cap reached) - Multiple files with other errors (unbound-method, unsafe-type-assertion) are separate clusters
560 lines
18 KiB
TypeScript
560 lines
18 KiB
TypeScript
import type { AgentProviderDefinition } from "@server/server/agent/provider-manifest";
|
|
import type {
|
|
AgentModelDefinition,
|
|
AgentProvider,
|
|
ProviderSnapshotEntry,
|
|
} from "@server/server/agent/agent-sdk-types";
|
|
import {
|
|
mergeProviderPreferences,
|
|
type FormPreferences,
|
|
type ProviderPreferences,
|
|
} from "./use-form-preferences";
|
|
|
|
export interface FormInitialValues {
|
|
serverId?: string | null;
|
|
provider?: AgentProvider;
|
|
modeId?: string | null;
|
|
model?: string | null;
|
|
thinkingOptionId?: string | null;
|
|
workingDir?: string;
|
|
}
|
|
|
|
export interface FormState {
|
|
serverId: string | null;
|
|
provider: AgentProvider | null;
|
|
modeId: string;
|
|
model: string;
|
|
thinkingOptionId: string;
|
|
workingDir: string;
|
|
}
|
|
|
|
export interface UserModifiedFields {
|
|
serverId: boolean;
|
|
provider: boolean;
|
|
modeId: boolean;
|
|
model: boolean;
|
|
thinkingOptionId: boolean;
|
|
workingDir: boolean;
|
|
}
|
|
|
|
export interface AgentFormReducerState {
|
|
form: FormState;
|
|
userModified: UserModifiedFields;
|
|
}
|
|
|
|
export const INITIAL_USER_MODIFIED: UserModifiedFields = {
|
|
serverId: false,
|
|
provider: false,
|
|
modeId: false,
|
|
model: false,
|
|
thinkingOptionId: false,
|
|
workingDir: false,
|
|
};
|
|
|
|
type ProviderPrefs = NonNullable<FormPreferences["providerPreferences"]>[AgentProvider];
|
|
|
|
export const RESOLVABLE_PROVIDER_STATUSES = new Set<ProviderSnapshotEntry["status"]>([
|
|
"ready",
|
|
"loading",
|
|
]);
|
|
export const SELECTABLE_PROVIDER_STATUSES = new Set<ProviderSnapshotEntry["status"]>(["ready"]);
|
|
|
|
export type AgentFormAction =
|
|
| {
|
|
type: "RESOLVE";
|
|
initialValues: FormInitialValues | undefined;
|
|
preferences: FormPreferences | null;
|
|
availableModels: AgentModelDefinition[] | null;
|
|
allowedProviderMap: Map<AgentProvider, AgentProviderDefinition>;
|
|
}
|
|
| { type: "SET_SERVER_ID"; value: string | null }
|
|
| { type: "SET_SERVER_ID_FROM_USER"; value: string | null }
|
|
| {
|
|
type: "SET_PROVIDER_FROM_USER";
|
|
provider: AgentProvider;
|
|
providerModels: AgentModelDefinition[] | null;
|
|
providerDef: AgentProviderDefinition | undefined;
|
|
providerPrefs: ProviderPrefs | undefined;
|
|
}
|
|
| {
|
|
type: "SET_PROVIDER_AND_MODEL_FROM_USER";
|
|
provider: AgentProvider;
|
|
modelId: string;
|
|
providerDef: AgentProviderDefinition | undefined;
|
|
providerModels: AgentModelDefinition[] | null;
|
|
}
|
|
| { type: "SET_MODE_FROM_USER"; modeId: string }
|
|
| {
|
|
type: "SET_MODEL_FROM_USER";
|
|
modelId: string;
|
|
availableModels: AgentModelDefinition[] | null;
|
|
}
|
|
| { type: "SET_THINKING_OPTION_FROM_USER"; thinkingOptionId: string }
|
|
| { type: "SET_WORKING_DIR"; value: string }
|
|
| { type: "SET_WORKING_DIR_FROM_USER"; value: string }
|
|
| { type: "AUTO_SELECT_SERVER"; candidateServerId: string }
|
|
| { type: "RESET" };
|
|
|
|
export function normalizeSelectedModelId(modelId: string | null | undefined): string {
|
|
return typeof modelId === "string" ? modelId.trim() : "";
|
|
}
|
|
|
|
export function resolveDefaultModel(
|
|
availableModels: AgentModelDefinition[] | null,
|
|
): AgentModelDefinition | null {
|
|
if (!availableModels || availableModels.length === 0) return null;
|
|
return availableModels.find((model) => model.isDefault) ?? availableModels[0] ?? null;
|
|
}
|
|
|
|
export function resolveDefaultModelId(availableModels: AgentModelDefinition[] | null): string {
|
|
return resolveDefaultModel(availableModels)?.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)
|
|
);
|
|
}
|
|
|
|
export 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 ?? thinkingOptions[0]?.id ?? "";
|
|
}
|
|
|
|
export function mergeSelectedComposerPreferences(args: {
|
|
preferences: FormPreferences;
|
|
provider: AgentProvider;
|
|
updates: Partial<ProviderPreferences>;
|
|
}): FormPreferences {
|
|
return mergeProviderPreferences({
|
|
preferences: args.preferences,
|
|
provider: args.provider,
|
|
updates: args.updates,
|
|
});
|
|
}
|
|
|
|
export function combineInitialValues(
|
|
initialValues: FormInitialValues | undefined,
|
|
initialServerId: string | null,
|
|
): FormInitialValues | undefined {
|
|
const hasExplicitServerId = initialValues?.serverId !== undefined;
|
|
const serverIdFromOptions = initialServerId === null ? undefined : initialServerId;
|
|
|
|
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 hasFormStateChanged(prev: FormState, next: FormState): boolean {
|
|
return (
|
|
prev.serverId !== next.serverId ||
|
|
prev.provider !== next.provider ||
|
|
prev.modeId !== next.modeId ||
|
|
prev.model !== next.model ||
|
|
prev.thinkingOptionId !== next.thinkingOptionId ||
|
|
prev.workingDir !== next.workingDir
|
|
);
|
|
}
|
|
|
|
export function buildProviderDefinitionMap(
|
|
providerDefinitions: AgentProviderDefinition[],
|
|
): Map<AgentProvider, AgentProviderDefinition> {
|
|
return new Map<AgentProvider, AgentProviderDefinition>(
|
|
providerDefinitions.map((definition) => [definition.id, definition]),
|
|
);
|
|
}
|
|
|
|
export function buildProviderDefinitionMapForStatuses(args: {
|
|
snapshotEntries: ProviderSnapshotEntry[] | undefined;
|
|
providerDefinitions: AgentProviderDefinition[];
|
|
statuses: ReadonlySet<ProviderSnapshotEntry["status"]>;
|
|
}): Map<AgentProvider, AgentProviderDefinition> {
|
|
if (!args.snapshotEntries?.length) {
|
|
return buildProviderDefinitionMap(args.providerDefinitions);
|
|
}
|
|
|
|
const matchingProviders = new Set(
|
|
args.snapshotEntries
|
|
.filter((entry) => args.statuses.has(entry.status) && entry.enabled)
|
|
.map((entry) => entry.provider),
|
|
);
|
|
|
|
return buildProviderDefinitionMap(
|
|
args.providerDefinitions.filter((definition) => matchingProviders.has(definition.id)),
|
|
);
|
|
}
|
|
|
|
function resolveProvider(input: {
|
|
currentProvider: AgentProvider | null;
|
|
userModified: boolean;
|
|
initialValues: FormInitialValues | undefined;
|
|
preferences: FormPreferences | null;
|
|
allowedProviderMap: Map<AgentProvider, AgentProviderDefinition>;
|
|
}): AgentProvider | null {
|
|
const { currentProvider, userModified, initialValues, preferences, allowedProviderMap } = input;
|
|
if (userModified) {
|
|
if (
|
|
currentProvider &&
|
|
allowedProviderMap.size > 0 &&
|
|
!allowedProviderMap.has(currentProvider)
|
|
) {
|
|
return null;
|
|
}
|
|
return currentProvider;
|
|
}
|
|
if (initialValues?.provider && allowedProviderMap.has(initialValues.provider)) {
|
|
return initialValues.provider;
|
|
}
|
|
if (preferences?.provider && allowedProviderMap.has(preferences.provider)) {
|
|
return preferences.provider;
|
|
}
|
|
if (currentProvider && allowedProviderMap.size > 0 && !allowedProviderMap.has(currentProvider)) {
|
|
return null;
|
|
}
|
|
return currentProvider;
|
|
}
|
|
|
|
function resolveModeId(input: {
|
|
provider: AgentProvider | null;
|
|
userModified: boolean;
|
|
currentModeId: string;
|
|
initialValues: FormInitialValues | undefined;
|
|
providerDef: AgentProviderDefinition | undefined;
|
|
providerPrefs: ProviderPrefs | undefined;
|
|
}): string {
|
|
const { provider, userModified, currentModeId, initialValues, providerDef, providerPrefs } =
|
|
input;
|
|
if (userModified) return currentModeId;
|
|
if (!provider) return "";
|
|
const validModeIds = providerDef?.modes.map((m) => m.id) ?? [];
|
|
if (
|
|
typeof initialValues?.modeId === "string" &&
|
|
initialValues.modeId.length > 0 &&
|
|
validModeIds.includes(initialValues.modeId)
|
|
) {
|
|
return initialValues.modeId;
|
|
}
|
|
if (providerPrefs?.mode && validModeIds.includes(providerPrefs.mode)) {
|
|
return providerPrefs.mode;
|
|
}
|
|
return providerDef?.defaultModeId ?? validModeIds[0] ?? "";
|
|
}
|
|
|
|
function resolveModelField(input: {
|
|
provider: AgentProvider | null;
|
|
userModified: boolean;
|
|
currentModel: string;
|
|
initialValues: FormInitialValues | undefined;
|
|
providerPrefs: ProviderPrefs | undefined;
|
|
availableModels: AgentModelDefinition[] | null;
|
|
}): string {
|
|
const { provider, userModified, currentModel, initialValues, providerPrefs, availableModels } =
|
|
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;
|
|
}
|
|
if (preferredModel) {
|
|
return !availableModels || isValidModel(preferredModel) ? preferredModel : defaultModelId;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function resolveThinkingOption(input: {
|
|
provider: AgentProvider | null;
|
|
userModified: boolean;
|
|
currentThinkingOptionId: string;
|
|
modelId: string;
|
|
initialValues: FormInitialValues | undefined;
|
|
providerPrefs: ProviderPrefs | undefined;
|
|
}): string {
|
|
const { provider, userModified, currentThinkingOptionId, modelId, initialValues, providerPrefs } =
|
|
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() ?? "")
|
|
: "";
|
|
if (initialThinkingOptionId.length > 0) return initialThinkingOptionId;
|
|
if (preferredThinking.length > 0) return preferredThinking;
|
|
return "";
|
|
}
|
|
|
|
export function resolveFormState(
|
|
initialValues: FormInitialValues | undefined,
|
|
preferences: FormPreferences | null,
|
|
availableModels: AgentModelDefinition[] | null,
|
|
userModified: UserModifiedFields,
|
|
currentState: FormState,
|
|
allowedProviderMap: Map<AgentProvider, AgentProviderDefinition>,
|
|
): FormState {
|
|
const result = { ...currentState };
|
|
|
|
result.provider = resolveProvider({
|
|
currentProvider: result.provider,
|
|
userModified: userModified.provider,
|
|
initialValues,
|
|
preferences,
|
|
allowedProviderMap,
|
|
});
|
|
|
|
const providerDef = result.provider ? allowedProviderMap.get(result.provider) : undefined;
|
|
const providerPrefs = result.provider
|
|
? preferences?.providerPreferences?.[result.provider]
|
|
: undefined;
|
|
|
|
result.modeId = resolveModeId({
|
|
provider: result.provider,
|
|
userModified: userModified.modeId,
|
|
currentModeId: result.modeId,
|
|
initialValues,
|
|
providerDef,
|
|
providerPrefs,
|
|
});
|
|
|
|
result.model = resolveModelField({
|
|
provider: result.provider,
|
|
userModified: userModified.model,
|
|
currentModel: result.model,
|
|
initialValues,
|
|
providerPrefs,
|
|
availableModels,
|
|
});
|
|
|
|
result.thinkingOptionId = resolveThinkingOption({
|
|
provider: result.provider,
|
|
userModified: userModified.thinkingOptionId,
|
|
currentThinkingOptionId: result.thinkingOptionId,
|
|
modelId: result.model,
|
|
initialValues,
|
|
providerPrefs,
|
|
});
|
|
|
|
if (result.provider && availableModels) {
|
|
result.thinkingOptionId = resolveThinkingOptionId({
|
|
availableModels,
|
|
modelId: result.model,
|
|
requestedThinkingOptionId: result.thinkingOptionId,
|
|
});
|
|
}
|
|
|
|
if (!userModified.serverId && initialValues?.serverId !== undefined) {
|
|
result.serverId = initialValues.serverId;
|
|
}
|
|
|
|
if (!userModified.workingDir && initialValues?.workingDir !== undefined) {
|
|
result.workingDir = initialValues.workingDir;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function pickNextModelForProvider(input: {
|
|
providerModels: AgentModelDefinition[] | null;
|
|
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;
|
|
}
|
|
return defaultModelId;
|
|
}
|
|
|
|
function pickNextModeForProvider(input: {
|
|
providerDef: AgentProviderDefinition | undefined;
|
|
providerPrefs: ProviderPrefs | undefined;
|
|
}): string {
|
|
const { providerDef, providerPrefs } = input;
|
|
const validModeIds = providerDef?.modes.map((m) => m.id) ?? [];
|
|
if (providerPrefs?.mode && validModeIds.includes(providerPrefs.mode)) {
|
|
return providerPrefs.mode;
|
|
}
|
|
return providerDef?.defaultModeId ?? "";
|
|
}
|
|
|
|
function pickNextThinkingOptionForProvider(input: {
|
|
providerModels: AgentModelDefinition[] | null;
|
|
providerPrefs: ProviderPrefs | undefined;
|
|
modelId: string;
|
|
}): string {
|
|
const { providerModels, providerPrefs, modelId } = input;
|
|
const preferredThinking = modelId
|
|
? (providerPrefs?.thinkingByModel?.[modelId]?.trim() ?? "")
|
|
: "";
|
|
return resolveThinkingOptionId({
|
|
availableModels: providerModels,
|
|
modelId,
|
|
requestedThinkingOptionId: preferredThinking,
|
|
});
|
|
}
|
|
|
|
export function resolveAgentForm(
|
|
state: AgentFormReducerState,
|
|
action: AgentFormAction,
|
|
): AgentFormReducerState {
|
|
switch (action.type) {
|
|
case "RESOLVE": {
|
|
const resolved = resolveFormState(
|
|
action.initialValues,
|
|
action.preferences,
|
|
action.availableModels,
|
|
state.userModified,
|
|
state.form,
|
|
action.allowedProviderMap,
|
|
);
|
|
if (!hasFormStateChanged(state.form, resolved)) return state;
|
|
return { ...state, form: resolved };
|
|
}
|
|
|
|
case "SET_SERVER_ID":
|
|
return { ...state, form: { ...state.form, serverId: action.value } };
|
|
|
|
case "SET_SERVER_ID_FROM_USER":
|
|
return {
|
|
form: { ...state.form, serverId: action.value },
|
|
userModified: { ...state.userModified, serverId: true },
|
|
};
|
|
|
|
case "SET_PROVIDER_FROM_USER": {
|
|
const nextModelId = pickNextModelForProvider({
|
|
providerModels: action.providerModels,
|
|
providerPrefs: action.providerPrefs,
|
|
});
|
|
const nextModeId = pickNextModeForProvider({
|
|
providerDef: action.providerDef,
|
|
providerPrefs: action.providerPrefs,
|
|
});
|
|
const nextThinkingOptionId = pickNextThinkingOptionForProvider({
|
|
providerModels: action.providerModels,
|
|
providerPrefs: action.providerPrefs,
|
|
modelId: nextModelId,
|
|
});
|
|
return {
|
|
form: {
|
|
...state.form,
|
|
provider: action.provider,
|
|
modeId: nextModeId,
|
|
model: nextModelId,
|
|
thinkingOptionId: nextThinkingOptionId,
|
|
},
|
|
userModified: { ...state.userModified, provider: true },
|
|
};
|
|
}
|
|
|
|
case "SET_PROVIDER_AND_MODEL_FROM_USER": {
|
|
const normalizedModelId = normalizeSelectedModelId(action.modelId);
|
|
const nextModelId = normalizedModelId || resolveDefaultModelId(action.providerModels);
|
|
const nextThinkingOptionId = resolveThinkingOptionId({
|
|
availableModels: action.providerModels,
|
|
modelId: nextModelId,
|
|
requestedThinkingOptionId: "",
|
|
});
|
|
return {
|
|
form: {
|
|
...state.form,
|
|
provider: action.provider,
|
|
model: nextModelId,
|
|
modeId: action.providerDef?.defaultModeId ?? "",
|
|
thinkingOptionId: nextThinkingOptionId,
|
|
},
|
|
userModified: { ...state.userModified, provider: true, model: true },
|
|
};
|
|
}
|
|
|
|
case "SET_MODE_FROM_USER":
|
|
return {
|
|
form: { ...state.form, modeId: action.modeId },
|
|
userModified: { ...state.userModified, modeId: true },
|
|
};
|
|
|
|
case "SET_MODEL_FROM_USER": {
|
|
const normalizedModelId = normalizeSelectedModelId(action.modelId);
|
|
const nextModelId = normalizedModelId || resolveDefaultModelId(action.availableModels);
|
|
const nextThinkingOptionId = resolveThinkingOptionId({
|
|
availableModels: action.availableModels,
|
|
modelId: nextModelId,
|
|
requestedThinkingOptionId: state.userModified.thinkingOptionId
|
|
? state.form.thinkingOptionId
|
|
: "",
|
|
});
|
|
return {
|
|
form: {
|
|
...state.form,
|
|
model: nextModelId,
|
|
thinkingOptionId: nextThinkingOptionId,
|
|
},
|
|
userModified: { ...state.userModified, model: true },
|
|
};
|
|
}
|
|
|
|
case "SET_THINKING_OPTION_FROM_USER":
|
|
return {
|
|
form: { ...state.form, thinkingOptionId: action.thinkingOptionId },
|
|
userModified: { ...state.userModified, thinkingOptionId: true },
|
|
};
|
|
|
|
case "SET_WORKING_DIR":
|
|
return { ...state, form: { ...state.form, workingDir: action.value } };
|
|
|
|
case "SET_WORKING_DIR_FROM_USER":
|
|
return {
|
|
form: { ...state.form, workingDir: action.value },
|
|
userModified: { ...state.userModified, workingDir: true },
|
|
};
|
|
|
|
case "AUTO_SELECT_SERVER":
|
|
if (state.form.serverId) return state;
|
|
return { ...state, form: { ...state.form, serverId: action.candidateServerId } };
|
|
|
|
case "RESET":
|
|
return { ...state, userModified: INITIAL_USER_MODIFIED };
|
|
default:
|
|
throw new Error("unreachable");
|
|
}
|
|
}
|