diff --git a/packages/app/src/components/combined-model-selector.tsx b/packages/app/src/components/combined-model-selector.tsx
index 9c87318b8..ad14b64a7 100644
--- a/packages/app/src/components/combined-model-selector.tsx
+++ b/packages/app/src/components/combined-model-selector.tsx
@@ -23,7 +23,6 @@ import type { AgentProvider } from "@getpaseo/protocol/agent-types";
import type { SheetHeader } from "@/components/adaptive-modal-sheet";
import { useProviderSettingsStore } from "@/stores/provider-settings-store";
import { Button } from "@/components/ui/button";
-import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
const IS_WEB = platformIsWeb;
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
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 =
| { kind: "all" }
@@ -87,6 +90,8 @@ interface CombinedModelSelectorProps {
}) => React.ReactNode;
onOpen?: () => void;
onClose?: () => void;
+ onRetryProvider?: (provider: AgentProvider) => void;
+ isRetryingProvider?: boolean;
disabled?: boolean;
serverId?: string | null;
}
@@ -101,7 +106,8 @@ interface SelectorContentProps {
onSelect: (provider: string, modelId: string) => void;
onToggleFavorite?: (provider: string, modelId: string) => void;
onDrillDown: (providerId: string, providerLabel: string) => void;
- serverId: string | null;
+ onRetryProvider?: (provider: AgentProvider) => void;
+ isRetryingProvider: boolean;
}
function normalizeSearchQuery(value: string): string {
@@ -418,26 +424,29 @@ function ProviderModelRows({
}
function ProviderErrorEmptyState({
- serverId,
providerId,
message,
+ onRetryProvider,
+ isRetryingProvider,
}: {
- serverId: string | null;
providerId: string;
message: string;
+ onRetryProvider?: (provider: AgentProvider) => void;
+ isRetryingProvider: boolean;
}) {
const { theme } = useUnistyles();
- const { refresh, isRefreshing } = useProvidersSnapshot(serverId);
const handleRetry = useCallback(() => {
- void refresh([providerId]);
- }, [refresh, providerId]);
+ onRetryProvider?.(providerId);
+ }, [onRetryProvider, providerId]);
return (
{message}
-
+ {onRetryProvider ? (
+
+ ) : null}
);
}
@@ -452,7 +461,8 @@ function SelectorContent({
onSelect,
onToggleFavorite,
onDrillDown,
- serverId,
+ onRetryProvider,
+ isRetryingProvider,
}: SelectorContentProps) {
const { theme } = useUnistyles();
const normalizedQuery = useMemo(() => normalizeSearchQuery(searchQuery), [searchQuery]);
@@ -502,9 +512,10 @@ function SelectorContent({
if (drillSelection.kind === "error") {
return (
);
}
@@ -556,6 +567,8 @@ export function CombinedModelSelector({
renderTrigger,
onOpen,
onClose,
+ onRetryProvider,
+ isRetryingProvider = false,
disabled = false,
serverId = null,
}: CombinedModelSelectorProps) {
@@ -630,8 +643,17 @@ export function CombinedModelSelector({
return undefined;
}
const provider = providers.find((entry) => entry.id === view.providerId);
- const modelCount = provider ? getProviderModelRows(provider).length : 0;
- return Math.min(80 + modelCount * 40, 400);
+ if (!provider || provider.modelSelection.kind !== "models") {
+ 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]);
const triggerLabel = useMemo(() => {
@@ -804,7 +826,8 @@ export function CombinedModelSelector({
onSelect={handleSelect}
onToggleFavorite={onToggleFavorite}
onDrillDown={handleDrillDown}
- serverId={serverId}
+ onRetryProvider={onRetryProvider}
+ isRetryingProvider={isRetryingProvider}
/>
) : (
diff --git a/packages/app/src/composer/agent-controls/index.tsx b/packages/app/src/composer/agent-controls/index.tsx
index dd2733aff..39b2375d3 100644
--- a/packages/app/src/composer/agent-controls/index.tsx
+++ b/packages/app/src/composer/agent-controls/index.tsx
@@ -24,6 +24,7 @@ import { getProviderIcon } from "@/components/provider-icons";
import { CombinedModelSelector } from "@/components/combined-model-selector";
import {
buildProviderSelectorProviders,
+ buildSelectableProviderSelectorProviders,
type ProviderSelectorProvider,
} from "@/provider-selection/provider-selection";
import { useSessionStore } from "@/stores/session-store";
@@ -91,6 +92,8 @@ interface ControlledAgentControlsProps {
onSetFeature?: (featureId: string, value: unknown) => void;
onDropdownClose?: () => void;
onModelSelectorOpen?: () => void;
+ onRetryModelProvider?: (provider: AgentProvider) => void;
+ isRetryingModelProvider?: boolean;
/** Extra elements rendered inline with the agent controls (desktop only). */
desktopExtras?: ReactNode;
modelSelectorServerId?: string | null;
@@ -117,6 +120,8 @@ export interface DraftAgentControlsProps {
onSetFeature?: (featureId: string, value: unknown) => void;
onDropdownClose?: () => void;
onModelSelectorOpen?: () => void;
+ onRetryModelProvider?: (provider: AgentProvider) => void;
+ isRetryingModelProvider?: boolean;
disabled?: boolean;
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
// show "gpt-5.5" instead of "openrouter/gpt-5.5". Full label still appears in
// the model picker.
@@ -411,6 +405,8 @@ function ControlledAgentControls({
onSetFeature,
onDropdownClose,
onModelSelectorOpen,
+ onRetryModelProvider,
+ isRetryingModelProvider = false,
desktopExtras,
modelSelectorServerId = null,
}: ControlledAgentControlsProps) {
@@ -432,7 +428,6 @@ function ControlledAgentControls({
);
const displayProvider = findOptionLabel(providerOptions, selectedProviderId, "Provider");
- const displayModel = resolveDisplayModel(isModelLoading, modelOptions, selectedModelId);
const formattedThinkingOptions = useMemo(
() => toThinkingControlOptions(thinkingOptions),
[thinkingOptions],
@@ -588,6 +583,8 @@ function ControlledAgentControls({
onToggleFavoriteModel={onToggleFavoriteModel}
onDropdownClose={onDropdownClose}
onModelSelectorOpen={onModelSelectorOpen}
+ onRetryModelProvider={onRetryModelProvider}
+ isRetryingModelProvider={isRetryingModelProvider}
favoriteKeys={favoriteKeys}
disabled={disabled}
isModelLoading={isModelLoading}
@@ -599,7 +596,6 @@ function ControlledAgentControls({
comboboxProviderOptions={comboboxProviderOptions}
comboboxThinkingOptions={comboboxThinkingOptions}
displayProvider={displayProvider}
- displayModel={displayModel}
displayThinking={displayThinking}
openSelector={openSelector}
providerAnchorRef={providerAnchorRef}
@@ -628,6 +624,8 @@ function ControlledAgentControls({
onToggleFavoriteModel={onToggleFavoriteModel}
onDropdownClose={onDropdownClose}
onModelSelectorOpen={onModelSelectorOpen}
+ onRetryModelProvider={onRetryModelProvider}
+ isRetryingModelProvider={isRetryingModelProvider}
favoriteKeys={favoriteKeys}
disabled={disabled}
isModelLoading={isModelLoading}
@@ -665,6 +663,8 @@ interface DesktopAgentControlsContentProps {
onToggleFavoriteModel?: (provider: string, modelId: string) => void;
onDropdownClose?: () => void;
onModelSelectorOpen?: () => void;
+ onRetryModelProvider?: (provider: AgentProvider) => void;
+ isRetryingModelProvider: boolean;
favoriteKeys: Set;
disabled: boolean;
isModelLoading: boolean;
@@ -676,7 +676,6 @@ interface DesktopAgentControlsContentProps {
comboboxProviderOptions: ComboboxOption[];
comboboxThinkingOptions: ComboboxOption[];
displayProvider: string;
- displayModel: string;
displayThinking: string;
openSelector: AgentControlSelector | null;
providerAnchorRef: RefObject;
@@ -717,6 +716,8 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
onToggleFavoriteModel,
onDropdownClose,
onModelSelectorOpen,
+ onRetryModelProvider,
+ isRetryingModelProvider,
favoriteKeys,
disabled,
isModelLoading,
@@ -728,7 +729,6 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
comboboxProviderOptions,
comboboxThinkingOptions,
displayProvider,
- displayModel,
displayThinking,
openSelector,
providerAnchorRef,
@@ -779,12 +779,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
) : null}
{canSelectModel ? (
-
+
@@ -870,6 +867,8 @@ interface SheetAgentControlsContentProps {
onToggleFavoriteModel?: (provider: string, modelId: string) => void;
onDropdownClose?: () => void;
onModelSelectorOpen?: () => void;
+ onRetryModelProvider?: (provider: AgentProvider) => void;
+ isRetryingModelProvider: boolean;
favoriteKeys: Set;
disabled: boolean;
isModelLoading: boolean;
@@ -906,6 +905,8 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
onToggleFavoriteModel,
onDropdownClose,
onModelSelectorOpen,
+ onRetryModelProvider,
+ isRetryingModelProvider,
favoriteKeys,
disabled,
isModelLoading,
@@ -992,6 +993,8 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
disabled={modelDisabled}
onOpen={onModelSelectorOpen}
onClose={onDropdownClose}
+ onRetryProvider={onRetryModelProvider}
+ isRetryingProvider={isRetryingModelProvider}
renderTrigger={renderModelTrigger}
serverId={modelSelectorServerId}
/>
@@ -1353,6 +1356,8 @@ export const AgentControls = memo(function AgentControls({
const {
entries: snapshotEntries,
isLoading: snapshotIsLoading,
+ isRefreshing: snapshotIsRefreshing,
+ refresh: refreshSnapshot,
refetchIfStale: refetchSnapshotIfStale,
} = useProvidersSnapshot(serverId, { cwd: agent?.cwd });
@@ -1373,14 +1378,15 @@ export const AgentControls = memo(function AgentControls({
() => buildAgentProviderModels(agent?.provider, models),
[agent?.provider, models],
);
- const agentModelSelectorProviders = useMemo(
- () =>
- buildProviderSelectorProviders({
- providerDefinitions: agentProviderDefinitions,
- modelsByProvider: agentProviderModels,
- }),
- [agentProviderDefinitions, agentProviderModels],
- );
+ const agentModelSelectorProviders = useMemo(() => {
+ if (snapshotSelectedEntry) {
+ return buildSelectableProviderSelectorProviders([snapshotSelectedEntry]);
+ }
+ return buildProviderSelectorProviders({
+ providerDefinitions: agentProviderDefinitions,
+ modelsByProvider: agentProviderModels,
+ });
+ }, [agentProviderDefinitions, agentProviderModels, snapshotSelectedEntry]);
const modelSelection = resolveAgentModelSelection({
models,
@@ -1504,6 +1510,13 @@ export const AgentControls = memo(function AgentControls({
refetchSnapshotIfStale(agentProvider);
}, [agentProvider, refetchSnapshotIfStale]);
+ const handleRetryModelProvider = useCallback(
+ (provider: AgentProvider) => {
+ void refreshSnapshot([provider]);
+ },
+ [refreshSnapshot],
+ );
+
const modeChip = useMemo(
() => ,
[serverId, agentId],
@@ -1529,6 +1542,8 @@ export const AgentControls = memo(function AgentControls({
onSetFeature={handleSetFeature}
isModelLoading={snapshotIsLoading || selectedProviderIsLoading}
onModelSelectorOpen={handleModelSelectorOpen}
+ onRetryModelProvider={handleRetryModelProvider}
+ isRetryingModelProvider={snapshotIsRefreshing}
onDropdownClose={onDropdownClose}
disabled={!client}
desktopExtras={modeChip}
@@ -1558,6 +1573,8 @@ export function DraftAgentControls({
onSetFeature,
onDropdownClose,
onModelSelectorOpen,
+ onRetryModelProvider,
+ isRetryingModelProvider = false,
disabled = false,
modelSelectorServerId = null,
}: DraftAgentControlsProps) {
@@ -1627,6 +1644,8 @@ export function DraftAgentControls({
disabled={disabled}
onOpen={onModelSelectorOpen}
onClose={onDropdownClose}
+ onRetryProvider={onRetryModelProvider}
+ isRetryingProvider={isRetryingModelProvider}
serverId={modelSelectorServerId}
/>
{selectedProvider ? (
@@ -1638,6 +1657,8 @@ export function DraftAgentControls({
features={features}
onSetFeature={onSetFeature}
onDropdownClose={onDropdownClose}
+ onRetryModelProvider={onRetryModelProvider}
+ isRetryingModelProvider={isRetryingModelProvider}
disabled={disabled}
desktopExtras={draftModeChip}
/>
@@ -1663,6 +1684,8 @@ export function DraftAgentControls({
features={features}
onSetFeature={onSetFeature}
onModelSelectorOpen={onModelSelectorOpen}
+ onRetryModelProvider={onRetryModelProvider}
+ isRetryingModelProvider={isRetryingModelProvider}
disabled={disabled}
modelSelectorServerId={modelSelectorServerId}
/>
diff --git a/packages/app/src/composer/draft/input-draft-core.ts b/packages/app/src/composer/draft/input-draft-core.ts
index cbcf7b77b..ad7ce7b85 100644
--- a/packages/app/src/composer/draft/input-draft-core.ts
+++ b/packages/app/src/composer/draft/input-draft-core.ts
@@ -46,6 +46,8 @@ export function buildDraftAgentControls(input: {
onSetFeature,
onDropdownClose,
onModelSelectorOpen: formState.refetchProviderModelsIfStale,
+ onRetryModelProvider: formState.refreshProviderModels,
+ isRetryingModelProvider: formState.isProviderModelsRefreshing,
modelSelectorServerId: formState.selectedServerId,
};
}
diff --git a/packages/app/src/composer/draft/input-draft.live.test.tsx b/packages/app/src/composer/draft/input-draft.live.test.tsx
index f04d469e2..c4d554ee7 100644
--- a/packages/app/src/composer/draft/input-draft.live.test.tsx
+++ b/packages/app/src/composer/draft/input-draft.live.test.tsx
@@ -101,6 +101,7 @@ vi.mock("@/hooks/use-agent-form-state", () => ({
},
],
isAllModelsLoading: false,
+ isProviderModelsRefreshing: false,
availableThinkingOptions: [
{ id: "medium", label: "Medium" },
{ id: "high", label: "High", isDefault: true },
diff --git a/packages/app/src/hooks/use-agent-form-state.ts b/packages/app/src/hooks/use-agent-form-state.ts
index 552a289f0..95812bec9 100644
--- a/packages/app/src/hooks/use-agent-form-state.ts
+++ b/packages/app/src/hooks/use-agent-form-state.ts
@@ -69,10 +69,11 @@ export interface UseAgentFormStateResult {
allProviderModels: Map;
modelSelectorProviders: ProviderSelectorProvider[];
isAllModelsLoading: boolean;
+ isProviderModelsRefreshing: boolean;
availableThinkingOptions: NonNullable;
isModelLoading: boolean;
modelError: string | null;
- refreshProviderModels: () => void;
+ refreshProviderModels: (provider?: AgentProvider) => void;
refetchProviderModelsIfStale: () => void;
setProviderAndModelFromUser: (provider: AgentProvider, modelId: string) => void;
workingDirIsEmpty: boolean;
@@ -208,6 +209,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
const {
entries: snapshotEntries,
isLoading: snapshotIsLoading,
+ isRefreshing: snapshotIsRefreshing,
error: snapshotError,
refresh: refreshSnapshot,
refetchIfStale: refetchSnapshotIfStale,
@@ -475,9 +477,12 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
dispatch({ type: "SET_SERVER_ID", value });
}, []);
- const refreshProviderModels = useCallback(() => {
- void refreshSnapshot();
- }, [refreshSnapshot]);
+ const refreshProviderModels = useCallback(
+ (provider?: AgentProvider) => {
+ void refreshSnapshot(provider ? [provider] : undefined);
+ },
+ [refreshSnapshot],
+ );
const refetchProviderModelsIfStale = useCallback(() => {
refetchSnapshotIfStale(reducerStateRef.current.form.provider);
@@ -535,6 +540,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
allProviderModels,
modelSelectorProviders,
isAllModelsLoading,
+ isProviderModelsRefreshing: snapshotIsRefreshing,
availableThinkingOptions,
isModelLoading,
modelError,
@@ -568,6 +574,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
allProviderModels,
modelSelectorProviders,
isAllModelsLoading,
+ snapshotIsRefreshing,
availableThinkingOptions,
isModelLoading,
modelError,
diff --git a/packages/app/src/provider-selection/provider-selection.test.ts b/packages/app/src/provider-selection/provider-selection.test.ts
index 984195239..648caec4f 100644
--- a/packages/app/src/provider-selection/provider-selection.test.ts
+++ b/packages/app/src/provider-selection/provider-selection.test.ts
@@ -259,6 +259,27 @@ describe("combined model selector data", () => {
).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", () => {
expect(
resolveSubmissionReadiness({
diff --git a/packages/app/src/provider-selection/provider-selection.ts b/packages/app/src/provider-selection/provider-selection.ts
index a64bf23dd..797554e24 100644
--- a/packages/app/src/provider-selection/provider-selection.ts
+++ b/packages/app/src/provider-selection/provider-selection.ts
@@ -162,6 +162,9 @@ export function resolveSelectedModelLabel(input: {
if (provider.modelSelection.kind === "loading") {
return "Loading...";
}
+ if (provider.modelSelection.kind === "error") {
+ return "Error";
+ }
if (provider.modelSelection.kind !== "models") {
return "Select model";
}