fix(app): stop spawning providers per client by warming the snapshot store

The workspace screen was prefetching per-provider model lists via
`useProviderModels`, which fanned out N parallel
`list_provider_models_request`s that bypassed `ProviderSnapshotManager`
and spawned each provider fresh on every call (e.g. repeated copilot
ENOENT errors). The return value was never consumed.

Replace it with `useProvidersSnapshot(serverId, workspaceDirectory)` so
warmup goes through the shared, deduped, TTL-cached snapshot path that
consumers already read from.
This commit is contained in:
Mohamed Boudra
2026-04-17 15:05:28 +07:00
parent 13bfdd8cc1
commit 07d10f55f9
2 changed files with 5 additions and 53 deletions
@@ -1,49 +0,0 @@
import { useMemo } from "react";
import { useQueries } from "@tanstack/react-query";
import {
AGENT_PROVIDER_DEFINITIONS,
type AgentProviderDefinition,
} from "@server/server/agent/provider-manifest";
import type { AgentModelDefinition, AgentProvider } from "@server/server/agent/agent-sdk-types";
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
const STALE_TIME = 5 * 60 * 1000;
export function useProviderModels(serverId: string) {
const client = useHostRuntimeClient(serverId);
const isConnected = useHostRuntimeIsConnected(serverId);
const enabled = Boolean(serverId && client && isConnected);
const queries = useQueries({
queries: AGENT_PROVIDER_DEFINITIONS.map((def) => ({
queryKey: ["providerModels", serverId, def.id] as const,
enabled,
staleTime: STALE_TIME,
queryFn: async () => {
if (!client) {
throw new Error("Host is not connected");
}
const payload = await client.listProviderModels(def.id as AgentProvider);
if (payload.error) {
throw new Error(payload.error);
}
return payload.models ?? [];
},
})),
});
const allProviderModels = useMemo(() => {
const map = new Map<string, AgentModelDefinition[]>();
for (let i = 0; i < AGENT_PROVIDER_DEFINITIONS.length; i++) {
const query = queries[i];
if (query?.data) {
map.set(AGENT_PROVIDER_DEFINITIONS[i]!.id, query.data);
}
}
return map;
}, [queries]);
const isLoading = queries.some((q) => q.isLoading);
return { allProviderModels, isLoading };
}
@@ -64,7 +64,7 @@ import {
workspaceTabTargetsEqual,
} from "@/utils/workspace-tab-identity";
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
import { useProviderModels } from "@/hooks/use-provider-models";
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
import { useWorkspaceSetupStore } from "@/stores/workspace-setup-store";
import { useWorkspaceTerminalSessionRetention } from "@/terminal/hooks/use-workspace-terminal-session-retention";
import {
@@ -612,9 +612,6 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
const normalizedServerId = trimNonEmpty(decodeSegment(serverId)) ?? "";
// Prefetch provider models early so the model picker is warm by the time it opens
useProviderModels(normalizedServerId);
const normalizedWorkspaceId =
resolveWorkspaceRouteId({
routeWorkspaceId: workspaceId,
@@ -645,6 +642,10 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
);
const workspaceDirectory = workspaceAuthority?.workspaceDirectory ?? null;
const isMissingWorkspaceExecutionAuthority = Boolean(workspaceDescriptor && !workspaceAuthority);
// Warm the server-side provider snapshot for this workspace cwd so the model
// picker is ready when opened. Consumers share the same query cache key.
useProvidersSnapshot(normalizedServerId, workspaceDirectory);
const [pendingTerminalCreateInput, setPendingTerminalCreateInput] = useState<{
paneId?: string;
} | null>(null);