Show model selector provider errors

This commit is contained in:
Mohamed Boudra
2026-05-29 17:16:35 +07:00
parent 7ed0ce97df
commit 0803d5ff41
7 changed files with 128 additions and 48 deletions
@@ -23,7 +23,6 @@ import type { AgentProvider } from "@getpaseo/protocol/agent-types";
import type { SheetHeader } from "@/components/adaptive-modal-sheet"; import type { SheetHeader } from "@/components/adaptive-modal-sheet";
import { useProviderSettingsStore } from "@/stores/provider-settings-store"; import { useProviderSettingsStore } from "@/stores/provider-settings-store";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
const IS_WEB = platformIsWeb; const IS_WEB = platformIsWeb;
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox"; import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
@@ -66,6 +65,10 @@ import {
// TODO: this should be configured per provider in the provider manifest // TODO: this should be configured per provider in the provider manifest
const PROVIDERS_WITH_MODEL_DESCRIPTIONS = new Set(["opencode", "pi"]); const PROVIDERS_WITH_MODEL_DESCRIPTIONS = new Set(["opencode", "pi"]);
const DESKTOP_PROVIDER_VIEW_MIN_HEIGHT = 220;
const DESKTOP_PROVIDER_VIEW_MAX_HEIGHT = 400;
const DESKTOP_PROVIDER_VIEW_BASE_HEIGHT = 80;
const DESKTOP_MODEL_ROW_HEIGHT = 40;
type SelectorView = type SelectorView =
| { kind: "all" } | { kind: "all" }
@@ -87,6 +90,8 @@ interface CombinedModelSelectorProps {
}) => React.ReactNode; }) => React.ReactNode;
onOpen?: () => void; onOpen?: () => void;
onClose?: () => void; onClose?: () => void;
onRetryProvider?: (provider: AgentProvider) => void;
isRetryingProvider?: boolean;
disabled?: boolean; disabled?: boolean;
serverId?: string | null; serverId?: string | null;
} }
@@ -101,7 +106,8 @@ interface SelectorContentProps {
onSelect: (provider: string, modelId: string) => void; onSelect: (provider: string, modelId: string) => void;
onToggleFavorite?: (provider: string, modelId: string) => void; onToggleFavorite?: (provider: string, modelId: string) => void;
onDrillDown: (providerId: string, providerLabel: string) => void; onDrillDown: (providerId: string, providerLabel: string) => void;
serverId: string | null; onRetryProvider?: (provider: AgentProvider) => void;
isRetryingProvider: boolean;
} }
function normalizeSearchQuery(value: string): string { function normalizeSearchQuery(value: string): string {
@@ -418,26 +424,29 @@ function ProviderModelRows({
} }
function ProviderErrorEmptyState({ function ProviderErrorEmptyState({
serverId,
providerId, providerId,
message, message,
onRetryProvider,
isRetryingProvider,
}: { }: {
serverId: string | null;
providerId: string; providerId: string;
message: string; message: string;
onRetryProvider?: (provider: AgentProvider) => void;
isRetryingProvider: boolean;
}) { }) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const { refresh, isRefreshing } = useProvidersSnapshot(serverId);
const handleRetry = useCallback(() => { const handleRetry = useCallback(() => {
void refresh([providerId]); onRetryProvider?.(providerId);
}, [refresh, providerId]); }, [onRetryProvider, providerId]);
return ( return (
<View style={styles.emptyState}> <View style={styles.emptyState}>
<AlertTriangle size={theme.iconSize.md} color={theme.colors.foregroundMuted} /> <AlertTriangle size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
<Text style={styles.emptyStateText}>{message}</Text> <Text style={styles.emptyStateText}>{message}</Text>
<Button variant="default" size="sm" onPress={handleRetry} disabled={isRefreshing}> {onRetryProvider ? (
{isRefreshing ? "Retrying…" : "Retry"} <Button variant="default" size="sm" onPress={handleRetry} disabled={isRetryingProvider}>
</Button> {isRetryingProvider ? "Retrying…" : "Retry"}
</Button>
) : null}
</View> </View>
); );
} }
@@ -452,7 +461,8 @@ function SelectorContent({
onSelect, onSelect,
onToggleFavorite, onToggleFavorite,
onDrillDown, onDrillDown,
serverId, onRetryProvider,
isRetryingProvider,
}: SelectorContentProps) { }: SelectorContentProps) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const normalizedQuery = useMemo(() => normalizeSearchQuery(searchQuery), [searchQuery]); const normalizedQuery = useMemo(() => normalizeSearchQuery(searchQuery), [searchQuery]);
@@ -502,9 +512,10 @@ function SelectorContent({
if (drillSelection.kind === "error") { if (drillSelection.kind === "error") {
return ( return (
<ProviderErrorEmptyState <ProviderErrorEmptyState
serverId={serverId}
providerId={view.providerId} providerId={view.providerId}
message={drillSelection.message} message={drillSelection.message}
onRetryProvider={onRetryProvider}
isRetryingProvider={isRetryingProvider}
/> />
); );
} }
@@ -556,6 +567,8 @@ export function CombinedModelSelector({
renderTrigger, renderTrigger,
onOpen, onOpen,
onClose, onClose,
onRetryProvider,
isRetryingProvider = false,
disabled = false, disabled = false,
serverId = null, serverId = null,
}: CombinedModelSelectorProps) { }: CombinedModelSelectorProps) {
@@ -630,8 +643,17 @@ export function CombinedModelSelector({
return undefined; return undefined;
} }
const provider = providers.find((entry) => entry.id === view.providerId); const provider = providers.find((entry) => entry.id === view.providerId);
const modelCount = provider ? getProviderModelRows(provider).length : 0; if (!provider || provider.modelSelection.kind !== "models") {
return Math.min(80 + modelCount * 40, 400); return DESKTOP_PROVIDER_VIEW_MIN_HEIGHT;
}
const modelCount = getProviderModelRows(provider).length;
return Math.min(
Math.max(
DESKTOP_PROVIDER_VIEW_MIN_HEIGHT,
DESKTOP_PROVIDER_VIEW_BASE_HEIGHT + modelCount * DESKTOP_MODEL_ROW_HEIGHT,
),
DESKTOP_PROVIDER_VIEW_MAX_HEIGHT,
);
}, [providers, view]); }, [providers, view]);
const triggerLabel = useMemo(() => { const triggerLabel = useMemo(() => {
@@ -804,7 +826,8 @@ export function CombinedModelSelector({
onSelect={handleSelect} onSelect={handleSelect}
onToggleFavorite={onToggleFavorite} onToggleFavorite={onToggleFavorite}
onDrillDown={handleDrillDown} onDrillDown={handleDrillDown}
serverId={serverId} onRetryProvider={onRetryProvider}
isRetryingProvider={isRetryingProvider}
/> />
) : ( ) : (
<View style={styles.sheetLoadingState}> <View style={styles.sheetLoadingState}>
@@ -24,6 +24,7 @@ import { getProviderIcon } from "@/components/provider-icons";
import { CombinedModelSelector } from "@/components/combined-model-selector"; import { CombinedModelSelector } from "@/components/combined-model-selector";
import { import {
buildProviderSelectorProviders, buildProviderSelectorProviders,
buildSelectableProviderSelectorProviders,
type ProviderSelectorProvider, type ProviderSelectorProvider,
} from "@/provider-selection/provider-selection"; } from "@/provider-selection/provider-selection";
import { useSessionStore } from "@/stores/session-store"; import { useSessionStore } from "@/stores/session-store";
@@ -91,6 +92,8 @@ interface ControlledAgentControlsProps {
onSetFeature?: (featureId: string, value: unknown) => void; onSetFeature?: (featureId: string, value: unknown) => void;
onDropdownClose?: () => void; onDropdownClose?: () => void;
onModelSelectorOpen?: () => void; onModelSelectorOpen?: () => void;
onRetryModelProvider?: (provider: AgentProvider) => void;
isRetryingModelProvider?: boolean;
/** Extra elements rendered inline with the agent controls (desktop only). */ /** Extra elements rendered inline with the agent controls (desktop only). */
desktopExtras?: ReactNode; desktopExtras?: ReactNode;
modelSelectorServerId?: string | null; modelSelectorServerId?: string | null;
@@ -117,6 +120,8 @@ export interface DraftAgentControlsProps {
onSetFeature?: (featureId: string, value: unknown) => void; onSetFeature?: (featureId: string, value: unknown) => void;
onDropdownClose?: () => void; onDropdownClose?: () => void;
onModelSelectorOpen?: () => void; onModelSelectorOpen?: () => void;
onRetryModelProvider?: (provider: AgentProvider) => void;
isRetryingModelProvider?: boolean;
disabled?: boolean; disabled?: boolean;
modelSelectorServerId?: string | null; modelSelectorServerId?: string | null;
} }
@@ -175,17 +180,6 @@ function getFeatureIconColor(
} }
} }
function resolveDisplayModel(
isModelLoading: boolean,
modelOptions: AgentControlOption[] | undefined,
selectedModelId: string | undefined,
) {
if (isModelLoading && (!modelOptions || modelOptions.length === 0)) {
return "Loading models...";
}
return findOptionLabel(modelOptions, selectedModelId, "Select model");
}
// Mobile agent controls only — strip namespace prefix so providers like OpenCode // Mobile agent controls only — strip namespace prefix so providers like OpenCode
// show "gpt-5.5" instead of "openrouter/gpt-5.5". Full label still appears in // show "gpt-5.5" instead of "openrouter/gpt-5.5". Full label still appears in
// the model picker. // the model picker.
@@ -411,6 +405,8 @@ function ControlledAgentControls({
onSetFeature, onSetFeature,
onDropdownClose, onDropdownClose,
onModelSelectorOpen, onModelSelectorOpen,
onRetryModelProvider,
isRetryingModelProvider = false,
desktopExtras, desktopExtras,
modelSelectorServerId = null, modelSelectorServerId = null,
}: ControlledAgentControlsProps) { }: ControlledAgentControlsProps) {
@@ -432,7 +428,6 @@ function ControlledAgentControls({
); );
const displayProvider = findOptionLabel(providerOptions, selectedProviderId, "Provider"); const displayProvider = findOptionLabel(providerOptions, selectedProviderId, "Provider");
const displayModel = resolveDisplayModel(isModelLoading, modelOptions, selectedModelId);
const formattedThinkingOptions = useMemo( const formattedThinkingOptions = useMemo(
() => toThinkingControlOptions(thinkingOptions), () => toThinkingControlOptions(thinkingOptions),
[thinkingOptions], [thinkingOptions],
@@ -588,6 +583,8 @@ function ControlledAgentControls({
onToggleFavoriteModel={onToggleFavoriteModel} onToggleFavoriteModel={onToggleFavoriteModel}
onDropdownClose={onDropdownClose} onDropdownClose={onDropdownClose}
onModelSelectorOpen={onModelSelectorOpen} onModelSelectorOpen={onModelSelectorOpen}
onRetryModelProvider={onRetryModelProvider}
isRetryingModelProvider={isRetryingModelProvider}
favoriteKeys={favoriteKeys} favoriteKeys={favoriteKeys}
disabled={disabled} disabled={disabled}
isModelLoading={isModelLoading} isModelLoading={isModelLoading}
@@ -599,7 +596,6 @@ function ControlledAgentControls({
comboboxProviderOptions={comboboxProviderOptions} comboboxProviderOptions={comboboxProviderOptions}
comboboxThinkingOptions={comboboxThinkingOptions} comboboxThinkingOptions={comboboxThinkingOptions}
displayProvider={displayProvider} displayProvider={displayProvider}
displayModel={displayModel}
displayThinking={displayThinking} displayThinking={displayThinking}
openSelector={openSelector} openSelector={openSelector}
providerAnchorRef={providerAnchorRef} providerAnchorRef={providerAnchorRef}
@@ -628,6 +624,8 @@ function ControlledAgentControls({
onToggleFavoriteModel={onToggleFavoriteModel} onToggleFavoriteModel={onToggleFavoriteModel}
onDropdownClose={onDropdownClose} onDropdownClose={onDropdownClose}
onModelSelectorOpen={onModelSelectorOpen} onModelSelectorOpen={onModelSelectorOpen}
onRetryModelProvider={onRetryModelProvider}
isRetryingModelProvider={isRetryingModelProvider}
favoriteKeys={favoriteKeys} favoriteKeys={favoriteKeys}
disabled={disabled} disabled={disabled}
isModelLoading={isModelLoading} isModelLoading={isModelLoading}
@@ -665,6 +663,8 @@ interface DesktopAgentControlsContentProps {
onToggleFavoriteModel?: (provider: string, modelId: string) => void; onToggleFavoriteModel?: (provider: string, modelId: string) => void;
onDropdownClose?: () => void; onDropdownClose?: () => void;
onModelSelectorOpen?: () => void; onModelSelectorOpen?: () => void;
onRetryModelProvider?: (provider: AgentProvider) => void;
isRetryingModelProvider: boolean;
favoriteKeys: Set<string>; favoriteKeys: Set<string>;
disabled: boolean; disabled: boolean;
isModelLoading: boolean; isModelLoading: boolean;
@@ -676,7 +676,6 @@ interface DesktopAgentControlsContentProps {
comboboxProviderOptions: ComboboxOption[]; comboboxProviderOptions: ComboboxOption[];
comboboxThinkingOptions: ComboboxOption[]; comboboxThinkingOptions: ComboboxOption[];
displayProvider: string; displayProvider: string;
displayModel: string;
displayThinking: string; displayThinking: string;
openSelector: AgentControlSelector | null; openSelector: AgentControlSelector | null;
providerAnchorRef: RefObject<View | null>; providerAnchorRef: RefObject<View | null>;
@@ -717,6 +716,8 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
onToggleFavoriteModel, onToggleFavoriteModel,
onDropdownClose, onDropdownClose,
onModelSelectorOpen, onModelSelectorOpen,
onRetryModelProvider,
isRetryingModelProvider,
favoriteKeys, favoriteKeys,
disabled, disabled,
isModelLoading, isModelLoading,
@@ -728,7 +729,6 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
comboboxProviderOptions, comboboxProviderOptions,
comboboxThinkingOptions, comboboxThinkingOptions,
displayProvider, displayProvider,
displayModel,
displayThinking, displayThinking,
openSelector, openSelector,
providerAnchorRef, providerAnchorRef,
@@ -779,12 +779,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
) : null} ) : null}
{canSelectModel ? ( {canSelectModel ? (
<Tooltip <Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
key={`model-${displayModel}`}
delayDuration={0}
enabledOnDesktop
enabledOnMobile={false}
>
<TooltipTrigger asChild triggerRefProp="ref"> <TooltipTrigger asChild triggerRefProp="ref">
<View> <View>
<CombinedModelSelector <CombinedModelSelector
@@ -798,6 +793,8 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
disabled={modelDisabled} disabled={modelDisabled}
onOpen={onModelSelectorOpen} onOpen={onModelSelectorOpen}
onClose={onDropdownClose} onClose={onDropdownClose}
onRetryProvider={onRetryModelProvider}
isRetryingProvider={isRetryingModelProvider}
serverId={modelSelectorServerId} serverId={modelSelectorServerId}
/> />
</View> </View>
@@ -870,6 +867,8 @@ interface SheetAgentControlsContentProps {
onToggleFavoriteModel?: (provider: string, modelId: string) => void; onToggleFavoriteModel?: (provider: string, modelId: string) => void;
onDropdownClose?: () => void; onDropdownClose?: () => void;
onModelSelectorOpen?: () => void; onModelSelectorOpen?: () => void;
onRetryModelProvider?: (provider: AgentProvider) => void;
isRetryingModelProvider: boolean;
favoriteKeys: Set<string>; favoriteKeys: Set<string>;
disabled: boolean; disabled: boolean;
isModelLoading: boolean; isModelLoading: boolean;
@@ -906,6 +905,8 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
onToggleFavoriteModel, onToggleFavoriteModel,
onDropdownClose, onDropdownClose,
onModelSelectorOpen, onModelSelectorOpen,
onRetryModelProvider,
isRetryingModelProvider,
favoriteKeys, favoriteKeys,
disabled, disabled,
isModelLoading, isModelLoading,
@@ -992,6 +993,8 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
disabled={modelDisabled} disabled={modelDisabled}
onOpen={onModelSelectorOpen} onOpen={onModelSelectorOpen}
onClose={onDropdownClose} onClose={onDropdownClose}
onRetryProvider={onRetryModelProvider}
isRetryingProvider={isRetryingModelProvider}
renderTrigger={renderModelTrigger} renderTrigger={renderModelTrigger}
serverId={modelSelectorServerId} serverId={modelSelectorServerId}
/> />
@@ -1353,6 +1356,8 @@ export const AgentControls = memo(function AgentControls({
const { const {
entries: snapshotEntries, entries: snapshotEntries,
isLoading: snapshotIsLoading, isLoading: snapshotIsLoading,
isRefreshing: snapshotIsRefreshing,
refresh: refreshSnapshot,
refetchIfStale: refetchSnapshotIfStale, refetchIfStale: refetchSnapshotIfStale,
} = useProvidersSnapshot(serverId, { cwd: agent?.cwd }); } = useProvidersSnapshot(serverId, { cwd: agent?.cwd });
@@ -1373,14 +1378,15 @@ export const AgentControls = memo(function AgentControls({
() => buildAgentProviderModels(agent?.provider, models), () => buildAgentProviderModels(agent?.provider, models),
[agent?.provider, models], [agent?.provider, models],
); );
const agentModelSelectorProviders = useMemo( const agentModelSelectorProviders = useMemo(() => {
() => if (snapshotSelectedEntry) {
buildProviderSelectorProviders({ return buildSelectableProviderSelectorProviders([snapshotSelectedEntry]);
providerDefinitions: agentProviderDefinitions, }
modelsByProvider: agentProviderModels, return buildProviderSelectorProviders({
}), providerDefinitions: agentProviderDefinitions,
[agentProviderDefinitions, agentProviderModels], modelsByProvider: agentProviderModels,
); });
}, [agentProviderDefinitions, agentProviderModels, snapshotSelectedEntry]);
const modelSelection = resolveAgentModelSelection({ const modelSelection = resolveAgentModelSelection({
models, models,
@@ -1504,6 +1510,13 @@ export const AgentControls = memo(function AgentControls({
refetchSnapshotIfStale(agentProvider); refetchSnapshotIfStale(agentProvider);
}, [agentProvider, refetchSnapshotIfStale]); }, [agentProvider, refetchSnapshotIfStale]);
const handleRetryModelProvider = useCallback(
(provider: AgentProvider) => {
void refreshSnapshot([provider]);
},
[refreshSnapshot],
);
const modeChip = useMemo( const modeChip = useMemo(
() => <AgentModeControl serverId={serverId} agentId={agentId} placement="toolbar" />, () => <AgentModeControl serverId={serverId} agentId={agentId} placement="toolbar" />,
[serverId, agentId], [serverId, agentId],
@@ -1529,6 +1542,8 @@ export const AgentControls = memo(function AgentControls({
onSetFeature={handleSetFeature} onSetFeature={handleSetFeature}
isModelLoading={snapshotIsLoading || selectedProviderIsLoading} isModelLoading={snapshotIsLoading || selectedProviderIsLoading}
onModelSelectorOpen={handleModelSelectorOpen} onModelSelectorOpen={handleModelSelectorOpen}
onRetryModelProvider={handleRetryModelProvider}
isRetryingModelProvider={snapshotIsRefreshing}
onDropdownClose={onDropdownClose} onDropdownClose={onDropdownClose}
disabled={!client} disabled={!client}
desktopExtras={modeChip} desktopExtras={modeChip}
@@ -1558,6 +1573,8 @@ export function DraftAgentControls({
onSetFeature, onSetFeature,
onDropdownClose, onDropdownClose,
onModelSelectorOpen, onModelSelectorOpen,
onRetryModelProvider,
isRetryingModelProvider = false,
disabled = false, disabled = false,
modelSelectorServerId = null, modelSelectorServerId = null,
}: DraftAgentControlsProps) { }: DraftAgentControlsProps) {
@@ -1627,6 +1644,8 @@ export function DraftAgentControls({
disabled={disabled} disabled={disabled}
onOpen={onModelSelectorOpen} onOpen={onModelSelectorOpen}
onClose={onDropdownClose} onClose={onDropdownClose}
onRetryProvider={onRetryModelProvider}
isRetryingProvider={isRetryingModelProvider}
serverId={modelSelectorServerId} serverId={modelSelectorServerId}
/> />
{selectedProvider ? ( {selectedProvider ? (
@@ -1638,6 +1657,8 @@ export function DraftAgentControls({
features={features} features={features}
onSetFeature={onSetFeature} onSetFeature={onSetFeature}
onDropdownClose={onDropdownClose} onDropdownClose={onDropdownClose}
onRetryModelProvider={onRetryModelProvider}
isRetryingModelProvider={isRetryingModelProvider}
disabled={disabled} disabled={disabled}
desktopExtras={draftModeChip} desktopExtras={draftModeChip}
/> />
@@ -1663,6 +1684,8 @@ export function DraftAgentControls({
features={features} features={features}
onSetFeature={onSetFeature} onSetFeature={onSetFeature}
onModelSelectorOpen={onModelSelectorOpen} onModelSelectorOpen={onModelSelectorOpen}
onRetryModelProvider={onRetryModelProvider}
isRetryingModelProvider={isRetryingModelProvider}
disabled={disabled} disabled={disabled}
modelSelectorServerId={modelSelectorServerId} modelSelectorServerId={modelSelectorServerId}
/> />
@@ -46,6 +46,8 @@ export function buildDraftAgentControls(input: {
onSetFeature, onSetFeature,
onDropdownClose, onDropdownClose,
onModelSelectorOpen: formState.refetchProviderModelsIfStale, onModelSelectorOpen: formState.refetchProviderModelsIfStale,
onRetryModelProvider: formState.refreshProviderModels,
isRetryingModelProvider: formState.isProviderModelsRefreshing,
modelSelectorServerId: formState.selectedServerId, modelSelectorServerId: formState.selectedServerId,
}; };
} }
@@ -101,6 +101,7 @@ vi.mock("@/hooks/use-agent-form-state", () => ({
}, },
], ],
isAllModelsLoading: false, isAllModelsLoading: false,
isProviderModelsRefreshing: false,
availableThinkingOptions: [ availableThinkingOptions: [
{ id: "medium", label: "Medium" }, { id: "medium", label: "Medium" },
{ id: "high", label: "High", isDefault: true }, { id: "high", label: "High", isDefault: true },
+11 -4
View File
@@ -69,10 +69,11 @@ export interface UseAgentFormStateResult {
allProviderModels: Map<string, AgentModelDefinition[]>; allProviderModels: Map<string, AgentModelDefinition[]>;
modelSelectorProviders: ProviderSelectorProvider[]; modelSelectorProviders: ProviderSelectorProvider[];
isAllModelsLoading: boolean; isAllModelsLoading: boolean;
isProviderModelsRefreshing: boolean;
availableThinkingOptions: NonNullable<AgentModelDefinition["thinkingOptions"]>; availableThinkingOptions: NonNullable<AgentModelDefinition["thinkingOptions"]>;
isModelLoading: boolean; isModelLoading: boolean;
modelError: string | null; modelError: string | null;
refreshProviderModels: () => void; refreshProviderModels: (provider?: AgentProvider) => void;
refetchProviderModelsIfStale: () => void; refetchProviderModelsIfStale: () => void;
setProviderAndModelFromUser: (provider: AgentProvider, modelId: string) => void; setProviderAndModelFromUser: (provider: AgentProvider, modelId: string) => void;
workingDirIsEmpty: boolean; workingDirIsEmpty: boolean;
@@ -208,6 +209,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
const { const {
entries: snapshotEntries, entries: snapshotEntries,
isLoading: snapshotIsLoading, isLoading: snapshotIsLoading,
isRefreshing: snapshotIsRefreshing,
error: snapshotError, error: snapshotError,
refresh: refreshSnapshot, refresh: refreshSnapshot,
refetchIfStale: refetchSnapshotIfStale, refetchIfStale: refetchSnapshotIfStale,
@@ -475,9 +477,12 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
dispatch({ type: "SET_SERVER_ID", value }); dispatch({ type: "SET_SERVER_ID", value });
}, []); }, []);
const refreshProviderModels = useCallback(() => { const refreshProviderModels = useCallback(
void refreshSnapshot(); (provider?: AgentProvider) => {
}, [refreshSnapshot]); void refreshSnapshot(provider ? [provider] : undefined);
},
[refreshSnapshot],
);
const refetchProviderModelsIfStale = useCallback(() => { const refetchProviderModelsIfStale = useCallback(() => {
refetchSnapshotIfStale(reducerStateRef.current.form.provider); refetchSnapshotIfStale(reducerStateRef.current.form.provider);
@@ -535,6 +540,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
allProviderModels, allProviderModels,
modelSelectorProviders, modelSelectorProviders,
isAllModelsLoading, isAllModelsLoading,
isProviderModelsRefreshing: snapshotIsRefreshing,
availableThinkingOptions, availableThinkingOptions,
isModelLoading, isModelLoading,
modelError, modelError,
@@ -568,6 +574,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
allProviderModels, allProviderModels,
modelSelectorProviders, modelSelectorProviders,
isAllModelsLoading, isAllModelsLoading,
snapshotIsRefreshing,
availableThinkingOptions, availableThinkingOptions,
isModelLoading, isModelLoading,
modelError, modelError,
@@ -259,6 +259,27 @@ describe("combined model selector data", () => {
).toBe("Default"); ).toBe("Default");
}); });
it("keeps provider snapshot errors visible in the selected trigger label", () => {
const providers = buildSelectableProviderSelectorProviders([
snapshotEntry({
provider: "opencode",
label: "OpenCode",
status: "error",
error: "OpenCode app.agents timed out after 10s",
models: [],
}),
]);
expect(
resolveSelectedModelLabel({
providers,
selectedProvider: "opencode",
selectedModel: "",
isLoading: false,
}),
).toBe("Error");
});
it("returns observable submission readiness reasons", () => { it("returns observable submission readiness reasons", () => {
expect( expect(
resolveSubmissionReadiness({ resolveSubmissionReadiness({
@@ -162,6 +162,9 @@ export function resolveSelectedModelLabel(input: {
if (provider.modelSelection.kind === "loading") { if (provider.modelSelection.kind === "loading") {
return "Loading..."; return "Loading...";
} }
if (provider.modelSelection.kind === "error") {
return "Error";
}
if (provider.modelSelection.kind !== "models") { if (provider.modelSelection.kind !== "models") {
return "Select model"; return "Select model";
} }