Organize composer into its own module

This commit is contained in:
Mohamed Boudra
2026-05-26 14:32:45 +07:00
parent 7e792e899a
commit 4231dbbba4
52 changed files with 935 additions and 706 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,372 @@
import { memo, useCallback, useMemo, useRef, useState, type ReactElement } from "react";
import { Pressable, Text, View, type PressableStateCallbackType } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { useShallow } from "zustand/shallow";
import { useStoreWithEqualityFn } from "zustand/traditional";
import {
ChevronDown,
ShieldAlert,
ShieldCheck,
ShieldOff,
ShieldQuestionMark,
} from "lucide-react-native";
import { type SheetHeader } from "@/components/adaptive-modal-sheet";
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
import { useSessionStore } from "@/stores/session-store";
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
import { resolveProviderDefinition } from "@/utils/provider-definitions";
import { useToast } from "@/contexts/toast-context";
import { useIsCompactFormFactor } from "@/constants/layout";
import { toErrorMessage } from "@/utils/error-messages";
import { formatAgentModeLabel } from "@/composer/agent-controls/utils";
import type { AgentMode, AgentProvider } from "@server/server/agent/agent-sdk-types";
import {
getModeVisuals,
type AgentModeColorTier,
type AgentProviderDefinition,
} from "@server/server/agent/provider-manifest";
export type AgentModeControlPlacement = "toolbar" | "footer";
function shouldRenderForPlacement(placement: AgentModeControlPlacement, isCompact: boolean) {
return placement === "footer" ? isCompact : !isCompact;
}
const MODE_ICONS = {
ShieldCheck,
ShieldAlert,
ShieldOff,
ShieldQuestionMark,
} as const;
interface ModePalette {
blue: { 500: string };
green: { 500: string };
red: { 500: string };
purple: { 500: string };
}
function getModeIconColor(colorTier: AgentModeColorTier | undefined, palette: ModePalette): string {
if (colorTier?.startsWith("#")) {
return colorTier;
}
switch (colorTier) {
case "safe":
return palette.green[500];
case "moderate":
return palette.blue[500];
case "dangerous":
return palette.red[500];
case "planning":
return palette.purple[500];
default:
return palette.blue[500];
}
}
interface ModeComboboxOptionProps {
option: ComboboxOption;
selected: boolean;
active: boolean;
onPress: () => void;
provider: string;
providerDefinitions: AgentProviderDefinition[];
iconColor: string;
}
function ModeComboboxOption({
option,
selected,
active,
onPress,
provider,
providerDefinitions,
iconColor,
}: ModeComboboxOptionProps) {
const visuals = getModeVisuals(provider, option.id, providerDefinitions);
const IconComponent = visuals?.icon ? MODE_ICONS[visuals.icon] : ShieldCheck;
const leadingSlot = useMemo(
() => <IconComponent size={16} color={iconColor} />,
[IconComponent, iconColor],
);
return (
<ComboboxItem
label={option.label}
selected={selected}
active={active}
onPress={onPress}
leadingSlot={leadingSlot}
/>
);
}
interface AgentModeControlViewProps {
provider: string;
providerDefinitions: AgentProviderDefinition[];
modeOptions: AgentMode[];
selectedModeId: string | null | undefined;
onSelectMode: (modeId: string) => void;
disabled?: boolean;
}
function normalizeSearchQuery(value: string): string {
return value.trim().toLowerCase();
}
function AgentModeControlView({
provider,
providerDefinitions,
modeOptions,
selectedModeId,
onSelectMode,
disabled = false,
}: AgentModeControlViewProps) {
const { theme } = useUnistyles();
const anchorRef = useRef<View>(null);
const [open, setOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const selectedMode = useMemo(() => {
if (modeOptions.length === 0) return null;
return modeOptions.find((m) => m.id === selectedModeId) ?? modeOptions[0];
}, [modeOptions, selectedModeId]);
const visuals = selectedMode
? getModeVisuals(provider, selectedMode.id, providerDefinitions)
: undefined;
const Icon = visuals?.icon ? MODE_ICONS[visuals.icon] : ShieldCheck;
const iconColor = getModeIconColor(visuals?.colorTier, theme.colors.palette);
const selectedModeLabel = selectedMode ? formatAgentModeLabel(selectedMode) : "";
const allOptions = useMemo<ComboboxOption[]>(
() => modeOptions.map((m) => ({ id: m.id, label: formatAgentModeLabel(m) })),
[modeOptions],
);
const options = useMemo<ComboboxOption[]>(() => {
const q = normalizeSearchQuery(searchQuery);
if (!q) return allOptions;
return allOptions.filter((o) => o.label.toLowerCase().includes(q));
}, [allOptions, searchQuery]);
const handleOpenChange = useCallback((next: boolean) => {
setOpen(next);
if (!next) setSearchQuery("");
}, []);
const handlePress = useCallback(() => handleOpenChange(!open), [handleOpenChange, open]);
const handleSelect = useCallback(
(id: string) => {
onSelectMode(id);
handleOpenChange(false);
},
[onSelectMode, handleOpenChange],
);
const renderOption = useCallback(
(args: {
option: ComboboxOption;
selected: boolean;
active: boolean;
onPress: () => void;
}): ReactElement => (
<ModeComboboxOption
option={args.option}
selected={args.selected}
active={args.active}
onPress={args.onPress}
provider={provider}
providerDefinitions={providerDefinitions}
iconColor={theme.colors.foreground}
/>
),
[provider, providerDefinitions, theme.colors.foreground],
);
const pressableStyle = useCallback(
({ pressed, hovered }: PressableStateCallbackType) => [
styles.chip,
hovered && styles.chipHovered,
(pressed || open) && styles.chipPressed,
disabled && styles.chipDisabled,
],
[open, disabled],
);
const labelStyle = useMemo(() => [styles.chipLabel, { color: iconColor }], [iconColor]);
const sheetHeader = useMemo<SheetHeader>(
() => ({
title: "Mode",
search: {
value: searchQuery,
onChange: setSearchQuery,
placeholder: "Search modes...",
testID: "mode-search-input",
},
}),
[searchQuery],
);
if (!selectedMode) return null;
return (
<>
<Pressable
ref={anchorRef}
collapsable={false}
disabled={disabled}
onPress={handlePress}
style={pressableStyle}
accessibilityRole="button"
accessibilityLabel={`Select agent mode (${selectedModeLabel})`}
testID="mode-control"
>
<Icon size={theme.iconSize.md} color={iconColor} />
<Text style={labelStyle}>{selectedModeLabel}</Text>
<ChevronDown size={theme.iconSize.sm} color={iconColor} />
</Pressable>
<Combobox
options={options}
value={selectedMode.id}
onSelect={handleSelect}
open={open}
onOpenChange={handleOpenChange}
anchorRef={anchorRef}
desktopPlacement="top-start"
header={sheetHeader}
renderOption={renderOption}
/>
</>
);
}
const EMPTY_MODES: AgentMode[] = [];
function compareAvailableModes(a: AgentMode[], b: AgentMode[]): boolean {
return a === b || JSON.stringify(a) === JSON.stringify(b);
}
interface AgentModeControlProps {
serverId: string;
agentId: string;
placement: AgentModeControlPlacement;
}
export const AgentModeControl = memo(function AgentModeControl({
serverId,
agentId,
placement,
}: AgentModeControlProps) {
const isCompact = useIsCompactFormFactor();
const slice = useSessionStore(
useShallow((state) => {
const agent = state.sessions[serverId]?.agents?.get(agentId);
if (!agent) return null;
return {
provider: agent.provider,
cwd: agent.cwd,
currentModeId: agent.currentModeId,
};
}),
);
const availableModes = useStoreWithEqualityFn(
useSessionStore,
(state) => state.sessions[serverId]?.agents?.get(agentId)?.availableModes ?? EMPTY_MODES,
compareAvailableModes,
);
const client = useSessionStore((state) => state.sessions[serverId]?.client ?? null);
const toast = useToast();
const { entries: snapshotEntries } = useProvidersSnapshot(serverId, { cwd: slice?.cwd });
const providerDefinitions = useMemo<AgentProviderDefinition[]>(() => {
if (!slice?.provider) return [];
const definition = resolveProviderDefinition(slice.provider, snapshotEntries);
return definition ? [definition] : [];
}, [slice?.provider, snapshotEntries]);
const handleSelectMode = useCallback(
(modeId: string) => {
if (!client) return;
void client.setAgentMode(agentId, modeId).catch((error) => {
console.warn("[AgentModeControl] setAgentMode failed", error);
toast.error(toErrorMessage(error));
});
},
[agentId, client, toast],
);
if (!slice || availableModes.length === 0) return null;
if (!shouldRenderForPlacement(placement, isCompact)) return null;
return (
<AgentModeControlView
provider={slice.provider}
providerDefinitions={providerDefinitions}
modeOptions={availableModes}
selectedModeId={slice.currentModeId}
onSelectMode={handleSelectMode}
disabled={!client}
/>
);
});
export interface DraftAgentModeControlProps {
selectedProvider: AgentProvider | null;
providerDefinitions: AgentProviderDefinition[];
modeOptions: AgentMode[];
selectedMode: string;
onSelectMode: (modeId: string) => void;
disabled?: boolean;
placement: AgentModeControlPlacement;
}
export function DraftAgentModeControl({
selectedProvider,
providerDefinitions,
modeOptions,
selectedMode,
onSelectMode,
disabled,
placement,
}: DraftAgentModeControlProps) {
const isCompact = useIsCompactFormFactor();
if (!selectedProvider || modeOptions.length === 0) return null;
if (!shouldRenderForPlacement(placement, isCompact)) return null;
return (
<AgentModeControlView
provider={selectedProvider}
providerDefinitions={providerDefinitions}
modeOptions={modeOptions}
selectedModeId={selectedMode}
onSelectMode={onSelectMode}
disabled={disabled}
/>
);
}
const styles = StyleSheet.create((theme) => ({
chip: {
height: 28,
flexDirection: "row",
alignItems: "center",
backgroundColor: "transparent",
gap: theme.spacing[1],
paddingHorizontal: theme.spacing[2],
borderRadius: theme.borderRadius["2xl"],
},
chipHovered: {
backgroundColor: theme.colors.surface2,
},
chipPressed: {
backgroundColor: theme.colors.surface0,
},
chipDisabled: {
opacity: 0.5,
},
chipLabel: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.normal,
},
}));
@@ -0,0 +1,31 @@
import { describe, expect, it } from "vitest";
import { resolveAgentControlsMode } from "./mode";
describe("resolveAgentControlsMode", () => {
it("uses ready mode when no controlled agent controls are provided", () => {
expect(resolveAgentControlsMode(undefined)).toBe("ready");
});
it("uses draft mode when controlled agent controls are provided", () => {
expect(
resolveAgentControlsMode({
providerDefinitions: [],
selectedProvider: "codex",
onSelectProvider: () => undefined,
modeOptions: [],
selectedMode: "",
onSelectMode: () => undefined,
models: [],
selectedModel: "",
onSelectModel: () => undefined,
isModelLoading: false,
modelSelectorProviders: [],
isAllModelsLoading: false,
onSelectProviderAndModel: () => undefined,
thinkingOptions: [],
selectedThinkingOptionId: "",
onSelectThinkingOption: () => undefined,
}),
).toBe("draft");
});
});
@@ -0,0 +1,5 @@
import type { DraftAgentControlsProps } from "@/composer/agent-controls";
export function resolveAgentControlsMode(agentControls?: DraftAgentControlsProps) {
return agentControls ? "draft" : "ready";
}
@@ -0,0 +1,31 @@
import { QueryClient, QueryObserver } from "@tanstack/react-query";
import { describe, expect, it } from "vitest";
import { isProviderModelsQueryLoading } from "./model-loading";
describe("isProviderModelsQueryLoading", () => {
it("does not treat a disabled pending query as loading", () => {
const queryClient = new QueryClient();
const observer = new QueryObserver(queryClient, {
queryKey: ["providerModels", "server-1", "__missing_provider__"],
enabled: false,
queryFn: async () => [],
});
const result = observer.getCurrentResult();
expect(result.isPending).toBe(true);
expect(result.isLoading).toBe(false);
expect(result.isFetching).toBe(false);
expect(isProviderModelsQueryLoading(result)).toBe(false);
});
it("treats an active fetch as loading", () => {
expect(
isProviderModelsQueryLoading({
isLoading: false,
isFetching: true,
}),
).toBe(true);
});
});
@@ -0,0 +1,8 @@
interface ProviderModelsQueryState {
isFetching: boolean;
isLoading: boolean;
}
export function isProviderModelsQueryLoading(input: ProviderModelsQueryState): boolean {
return input.isLoading || input.isFetching;
}
@@ -0,0 +1,199 @@
import { describe, expect, it } from "vitest";
import {
formatAgentModeLabel,
getFeatureHighlightColor,
getFeatureTooltip,
getAgentControlHint,
formatThinkingOptionLabel,
normalizeModelId,
resolveAgentModelSelection,
} from "./utils";
describe("getAgentControlHint", () => {
it("explains what each editable agent control does", () => {
expect(getAgentControlHint("thinking")).toBe("Thinking mode");
expect(getAgentControlHint("model")).toBe("Change model");
expect(getAgentControlHint("mode")).toBe("Change permission mode");
});
});
describe("feature metadata helpers", () => {
it("prefers explicit feature tooltip copy", () => {
expect(
getFeatureTooltip({
label: "Plan",
tooltip: "Toggle plan mode",
}),
).toBe("Toggle plan mode");
});
it("falls back to the feature label when no tooltip is provided", () => {
expect(
getFeatureTooltip({
label: "Custom",
}),
).toBe("Custom");
});
it("maps feature highlight colors by feature id", () => {
expect(getFeatureHighlightColor("fast_mode")).toBe("yellow");
expect(getFeatureHighlightColor("plan_mode")).toBe("blue");
expect(getFeatureHighlightColor("other")).toBe("default");
});
});
describe("normalizeModelId", () => {
it("treats empty values as unset", () => {
expect(normalizeModelId("")).toBeNull();
expect(normalizeModelId(undefined)).toBeNull();
});
it("returns trimmed model ids", () => {
expect(normalizeModelId(" gpt-5.1-codex ")).toBe("gpt-5.1-codex");
expect(normalizeModelId(" default ")).toBe("default");
});
});
describe("formatAgentModeLabel", () => {
it("sentence-cases provider mode labels", () => {
expect(formatAgentModeLabel({ id: "plan", label: "Plan" })).toBe("Plan");
expect(formatAgentModeLabel({ id: "full-access", label: "Full Access" })).toBe("Full access");
expect(formatAgentModeLabel({ id: "auto-review", label: "Auto-review" })).toBe("Auto-review");
expect(formatAgentModeLabel({ id: "read_only", label: "read_only" })).toBe("Read only");
expect(formatAgentModeLabel({ id: "acceptEdits", label: "acceptEdits" })).toBe("Accept edits");
});
it("splits compact mode ids when no provider label is available", () => {
expect(formatAgentModeLabel({ id: "auto-review" })).toBe("Auto review");
});
});
describe("formatThinkingOptionLabel", () => {
it("formats compact thinking option labels for display", () => {
expect(formatThinkingOptionLabel({ id: "none", label: "none" })).toBe("None");
expect(formatThinkingOptionLabel({ id: "low", label: "low" })).toBe("Low");
expect(formatThinkingOptionLabel({ id: "medium", label: "medium" })).toBe("Medium");
expect(formatThinkingOptionLabel({ id: "high", label: "high" })).toBe("High");
expect(formatThinkingOptionLabel({ id: "xhigh", label: "xhigh" })).toBe("Extra high");
});
it("sentence-cases split provider labels", () => {
expect(formatThinkingOptionLabel({ id: "extra_high", label: "extra_high" })).toBe("Extra high");
expect(formatThinkingOptionLabel({ id: "think-hard", label: "think-hard" })).toBe("Think hard");
expect(formatThinkingOptionLabel({ id: "xhigh", label: "XHigh" })).toBe("Extra high");
});
});
describe("resolveAgentModelSelection", () => {
it("prefers runtime model over configured model", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "a",
provider: "codex",
label: "Model A",
thinkingOptions: [{ id: "low", label: "Low" }],
defaultThinkingOptionId: "low",
},
],
runtimeModelId: "a",
configuredModelId: "b",
explicitThinkingOptionId: null,
});
expect(selection.activeModelId).toBe("a");
expect(selection.displayModel).toBe("Model A");
expect(selection.selectedThinkingId).toBe("low");
});
it("uses explicit thinking option when provided", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "a",
provider: "codex",
label: "Model A",
thinkingOptions: [
{ id: "low", label: "Low" },
{ id: "high", label: "High" },
],
defaultThinkingOptionId: "low",
},
],
runtimeModelId: "a",
configuredModelId: null,
explicitThinkingOptionId: "high",
});
expect(selection.selectedThinkingId).toBe("high");
expect(selection.displayThinking).toBe("High");
});
it("formats raw thinking labels in the selected model display", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "a",
provider: "claude",
label: "Model A",
thinkingOptions: [
{ id: "none", label: "none" },
{ id: "xhigh", label: "xhigh" },
],
},
],
runtimeModelId: "a",
configuredModelId: null,
explicitThinkingOptionId: "xhigh",
});
expect(selection.selectedThinkingId).toBe("xhigh");
expect(selection.displayThinking).toBe("Extra high");
});
it("falls back to the provider default model label instead of Auto", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "a",
provider: "codex",
label: "Model A",
isDefault: true,
thinkingOptions: [{ id: "low", label: "Low" }],
defaultThinkingOptionId: "low",
},
],
runtimeModelId: null,
configuredModelId: null,
explicitThinkingOptionId: null,
});
expect(selection.displayModel).toBe("Model A");
expect(selection.displayThinking).toBe("Low");
});
it("prefers the configured model when runtime model is not in the model list", () => {
const selection = resolveAgentModelSelection({
models: [
{
id: "default",
provider: "claude",
label: "Default (Sonnet 4.6)",
isDefault: true,
thinkingOptions: [
{ id: "low", label: "Low" },
{ id: "medium", label: "Medium" },
],
},
],
runtimeModelId: "claude-sonnet-4-6-20260101",
configuredModelId: "default",
explicitThinkingOptionId: null,
});
expect(selection.activeModelId).toBe("default");
expect(selection.displayModel).toBe("Default (Sonnet 4.6)");
expect(selection.selectedThinkingId).toBe("low");
expect(selection.displayThinking).toBe("Low");
});
});
@@ -0,0 +1,206 @@
import type { AgentFeature, AgentModelDefinition } from "@server/server/agent/agent-sdk-types";
export type ExplainedAgentControl = "mode" | "model" | "thinking";
export type FeatureHighlightColor = "blue" | "default" | "yellow";
export function getAgentControlHint(selector: ExplainedAgentControl): string {
switch (selector) {
case "thinking":
return "Thinking mode";
case "model":
return "Change model";
case "mode":
return "Change permission mode";
default:
throw new Error("unreachable");
}
}
export function normalizeModelId(modelId: string | null | undefined): string | null {
const normalized = typeof modelId === "string" ? modelId.trim() : "";
if (!normalized) {
return null;
}
return normalized;
}
export function getFeatureTooltip(feature: Pick<AgentFeature, "label" | "tooltip">): string {
return feature.tooltip ?? feature.label;
}
export function getFeatureHighlightColor(featureId: string): FeatureHighlightColor {
switch (featureId) {
case "fast_mode":
return "yellow";
case "plan_mode":
return "blue";
default:
return "default";
}
}
interface ControlLabelInput {
id: string;
label?: string | null;
}
function sentenceCase(value: string): string {
if (!value) {
return value;
}
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
}
function splitCompactLabel(value: string, splitHyphen: boolean): string {
const separatorPattern = splitHyphen ? /[_-]+/g : /_+/g;
return value
.replace(separatorPattern, " ")
.replace(/([a-z])([A-Z])/g, "$1 $2")
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
.replace(/\s+/g, " ")
.trim();
}
function formatControlLabel(option: ControlLabelInput, splitHyphen: boolean): string {
const rawLabel = (option.label ?? option.id).trim();
return sentenceCase(splitCompactLabel(rawLabel, splitHyphen));
}
export function formatAgentModeLabel(mode: ControlLabelInput): string {
return formatControlLabel(mode, mode.label == null);
}
export function formatThinkingOptionLabel(option: ControlLabelInput): string {
const rawLabel = (option.label ?? option.id).trim();
const compactId = option.id.replace(/[\s_-]+/g, "").toLowerCase();
const compactLabel = rawLabel.replace(/[\s_-]+/g, "").toLowerCase();
if (compactId === "xhigh" || compactLabel === "xhigh") {
return "Extra high";
}
return formatControlLabel(option, true);
}
function findModelById(
models: AgentModelDefinition[] | null,
modelId: string | null,
): AgentModelDefinition | null {
if (!models || !modelId) {
return null;
}
return models.find((model) => model.id === modelId) ?? null;
}
function getFallbackModel(models: AgentModelDefinition[] | null): AgentModelDefinition | null {
return models?.find((model) => model.isDefault) ?? models?.[0] ?? null;
}
function resolvePreferredModelId(
runtimeSelectedModel: AgentModelDefinition | null,
normalizedConfiguredModelId: string | null,
normalizedRuntimeModelId: string | null,
): string | null {
return runtimeSelectedModel?.id ?? normalizedConfiguredModelId ?? normalizedRuntimeModelId;
}
function pickSelectedModel(
models: AgentModelDefinition[] | null,
preferredModelId: string | null,
fallbackModel: AgentModelDefinition | null,
): AgentModelDefinition | null {
if (!models || !preferredModelId) {
return fallbackModel;
}
return findModelById(models, preferredModelId) ?? fallbackModel;
}
function resolveThinkingId(
explicitThinkingOptionId: string | null | undefined,
selectedModel: AgentModelDefinition | null,
): string | null {
if (explicitThinkingOptionId && explicitThinkingOptionId !== "default") {
return explicitThinkingOptionId;
}
return selectedModel?.defaultThinkingOptionId ?? null;
}
type ThinkingOption = NonNullable<AgentModelDefinition["thinkingOptions"]>[number];
function resolveEffectiveThinking(
thinkingOptions: ThinkingOption[] | null,
resolvedThinkingId: string | null,
): ThinkingOption | null {
const selectedThinking =
thinkingOptions?.find((option) => option.id === resolvedThinkingId) ?? null;
return selectedThinking ?? thinkingOptions?.[0] ?? null;
}
function resolveModelDisplay(
selectedModel: AgentModelDefinition | null,
preferredModelId: string | null,
fallbackModel: AgentModelDefinition | null,
): { activeModelId: string | null; displayModel: string } {
return {
activeModelId: selectedModel?.id ?? preferredModelId ?? null,
displayModel:
selectedModel?.label ?? preferredModelId ?? fallbackModel?.label ?? "Unknown model",
};
}
function resolveThinkingDisplay(
effectiveThinking: ThinkingOption | null,
selectedThinkingId: string | null,
): string {
if (effectiveThinking) {
return formatThinkingOptionLabel(effectiveThinking);
}
if (selectedThinkingId) {
return formatThinkingOptionLabel({ id: selectedThinkingId });
}
return "Unknown";
}
export function resolveAgentModelSelection(input: {
models: AgentModelDefinition[] | null;
runtimeModelId: string | null | undefined;
configuredModelId: string | null | undefined;
explicitThinkingOptionId: string | null | undefined;
}) {
const { models, runtimeModelId, configuredModelId, explicitThinkingOptionId } = input;
const normalizedRuntimeModelId = normalizeModelId(runtimeModelId);
const normalizedConfiguredModelId = normalizeModelId(configuredModelId);
const runtimeSelectedModel = findModelById(models, normalizedRuntimeModelId);
const preferredModelId = resolvePreferredModelId(
runtimeSelectedModel,
normalizedConfiguredModelId,
normalizedRuntimeModelId,
);
const fallbackModel = getFallbackModel(models);
const selectedModel = pickSelectedModel(models, preferredModelId, fallbackModel);
const { activeModelId, displayModel } = resolveModelDisplay(
selectedModel,
preferredModelId,
fallbackModel,
);
const thinkingOptions = selectedModel?.thinkingOptions ?? null;
const resolvedThinkingId = resolveThinkingId(explicitThinkingOptionId, selectedModel);
const effectiveThinking = resolveEffectiveThinking(thinkingOptions, resolvedThinkingId);
const selectedThinkingId = effectiveThinking?.id ?? null;
const displayThinking = resolveThinkingDisplay(effectiveThinking, selectedThinkingId);
return {
selectedModel,
activeModelId,
displayModel,
thinkingOptions,
selectedThinkingId,
displayThinking,
};
}