import { useCallback, useEffect, useMemo } from "react"; import { useMutation, useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; import type { AgentProvider, ProviderSnapshotEntry } from "@getpaseo/protocol/agent-types"; import type { DaemonClient } from "@getpaseo/client/internal/daemon-client"; import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime"; import { useSessionStore } from "@/stores/session-store"; import { queryClient as singletonQueryClient } from "@/query/query-client"; import { agentCommandsQueryRoot } from "@/hooks/agent-commands-query"; import { isProvidersSnapshotHomeScope, normalizeProvidersSnapshotCwd, providersSnapshotQueryKey, providersSnapshotQueryRoot, providersSnapshotRequestOptions, } from "@/hooks/providers-snapshot-query"; type GetProvidersSnapshotResult = Awaited>; type RefreshProvidersSnapshotResult = Awaited>; export { providersSnapshotQueryKey, providersSnapshotQueryRoot }; export type ProvidersSnapshotClient = Pick< DaemonClient, "getProvidersSnapshot" | "refreshProvidersSnapshot" >; export interface ProvidersSnapshotUpdateMessage { type: "providers_snapshot_update"; payload: { cwd?: string; entries: ProviderSnapshotEntry[]; generatedAt: string; }; } export async function fetchProvidersSnapshot(input: { client: ProvidersSnapshotClient; cwd: string | null; }): Promise { return input.client.getProvidersSnapshot(providersSnapshotRequestOptions({ cwd: input.cwd })); } export async function refreshAndApplyProvidersSnapshot(input: { client: ProvidersSnapshotClient; queryClient: QueryClient; serverId: string; cwd: string | null; providers?: AgentProvider[]; }): Promise { const refreshResult = await input.client.refreshProvidersSnapshot( providersSnapshotRequestOptions({ cwd: input.cwd, providers: input.providers }), ); const snapshot = await fetchProvidersSnapshot({ client: input.client, cwd: input.cwd }); input.queryClient.setQueryData(providersSnapshotQueryKey(input.serverId, input.cwd), snapshot); void input.queryClient.invalidateQueries({ queryKey: agentCommandsQueryRoot(input.serverId), exact: false, }); if (isProvidersSnapshotHomeScope(input.cwd)) { void input.queryClient.invalidateQueries({ queryKey: providersSnapshotQueryRoot(input.serverId), exact: false, }); } return refreshResult; } export function applyProvidersSnapshotUpdate(input: { serverId: string; queryClient: QueryClient; message: ProvidersSnapshotUpdateMessage; }): void { if (input.message.type !== "providers_snapshot_update") { return; } const queryKey = providersSnapshotQueryKey(input.serverId, input.message.payload.cwd); input.queryClient.setQueryData(queryKey, { entries: input.message.payload.entries, generatedAt: input.message.payload.generatedAt, requestId: "providers_snapshot_update", }); void input.queryClient.invalidateQueries({ queryKey: agentCommandsQueryRoot(input.serverId), exact: false, }); } export type SelectorOpenRefetchDecision = "refetch-stale" | "refetch-always"; export function selectorOpenRefetchDecision(input: { entries: ProviderSnapshotEntry[] | undefined; selectedProvider: AgentProvider | null | undefined; }): SelectorOpenRefetchDecision { if (!input.selectedProvider) { return "refetch-stale"; } const selectedEntry = input.entries?.find((entry) => entry.provider === input.selectedProvider); if (!selectedEntry || selectedEntry.status === "loading") { return "refetch-always"; } return "refetch-stale"; } interface UseProvidersSnapshotResult { entries: ProviderSnapshotEntry[] | undefined; isLoading: boolean; isFetching: boolean; isRefreshing: boolean; error: string | null; supportsSnapshot: boolean; refresh: (providers?: AgentProvider[]) => Promise; refetchIfStale: (selectedProvider?: AgentProvider | null) => void; } interface UseProvidersSnapshotOptions { enabled?: boolean; cwd?: string | null; } export function useProvidersSnapshot( serverId: string | null, options: UseProvidersSnapshotOptions = {}, ): UseProvidersSnapshotResult { const { t } = useTranslation(); const queryClient = useQueryClient(); const client = useHostRuntimeClient(serverId ?? ""); const isConnected = useHostRuntimeIsConnected(serverId ?? ""); const enabled = options.enabled ?? true; const cwd = normalizeProvidersSnapshotCwd(options.cwd); const supportsSnapshot = useSessionStore( (state) => state.sessions[serverId ?? ""]?.serverInfo?.features?.providersSnapshot === true, ); const queryKey = useMemo(() => providersSnapshotQueryKey(serverId, cwd), [cwd, serverId]); const snapshotQuery = useQuery({ queryKey, enabled: Boolean(enabled && supportsSnapshot && serverId && client && isConnected), staleTime: 60_000, queryFn: async () => { if (!client) { throw new Error(t("workspace.terminal.hostDisconnected")); } return fetchProvidersSnapshot({ client, cwd }); }, }); const refreshMutation = useMutation({ mutationFn: async (providers?: AgentProvider[]) => { if (!client || !serverId) { return; } await refreshAndApplyProvidersSnapshot({ client, queryClient, serverId, cwd, providers, }); }, }); const { mutateAsync: refreshSnapshot, isPending: isRefreshing } = refreshMutation; useEffect(() => { if (!enabled || !supportsSnapshot || !client || !isConnected || !serverId) { return; } return client.on("providers_snapshot_update", (message) => { if (message.type !== "providers_snapshot_update") { return; } applyProvidersSnapshotUpdate({ serverId, queryClient, message }); }); }, [client, enabled, isConnected, queryClient, serverId, supportsSnapshot]); const refresh = useCallback( async (providers?: AgentProvider[]) => { await refreshSnapshot(providers); }, [refreshSnapshot], ); const refetchIfStale = useCallback( (selectedProvider?: AgentProvider | null) => { const decision = selectorOpenRefetchDecision({ entries: snapshotQuery.data?.entries, selectedProvider, }); if (decision === "refetch-always") { void queryClient.refetchQueries({ queryKey, type: "active" }); return; } void queryClient.refetchQueries({ queryKey, type: "active", stale: true }); }, [queryClient, queryKey, snapshotQuery.data?.entries], ); return { entries: snapshotQuery.data?.entries ?? undefined, isLoading: snapshotQuery.isLoading, isFetching: snapshotQuery.isFetching, isRefreshing, error: snapshotQuery.error instanceof Error ? snapshotQuery.error.message : null, supportsSnapshot, refresh, refetchIfStale, }; } export function prefetchProvidersSnapshot( serverId: string, client: DaemonClient, options: { cwd?: string | null } = {}, ): void { const cwd = normalizeProvidersSnapshotCwd(options.cwd); const queryKey = providersSnapshotQueryKey(serverId, cwd); void singletonQueryClient.prefetchQuery({ queryKey, staleTime: 60_000, queryFn: () => fetchProvidersSnapshot({ client, cwd }), }); }