fix(providers): prevent stale models during refresh

Provider settings sheets can remain mounted while hidden, so the old display cache could survive into a different provider. Scope the cache to the active provider identity and remount the sheet when that identity changes.
This commit is contained in:
Mohamed Boudra
2026-06-25 14:11:25 +07:00
parent f13c496ebc
commit 2692211ef9
4 changed files with 126 additions and 14 deletions

View File

@@ -0,0 +1,69 @@
import { describe, expect, it } from "vitest";
import type { AgentModelDefinition } from "@getpaseo/protocol/agent-types";
import {
resolveProviderDiscoveredModels,
type ProviderDiscoveredModelsCache,
} from "./provider-diagnostic-models";
const piModel: AgentModelDefinition = {
provider: "pi",
id: "pi/model",
label: "Pi Model",
};
const grokModel: AgentModelDefinition = {
provider: "grok",
id: "grok-build",
label: "Grok Build",
};
function resolveModels(input: {
serverId?: string;
provider: string;
currentModels?: AgentModelDefinition[];
loading?: boolean;
cache?: ProviderDiscoveredModelsCache | null;
}) {
return resolveProviderDiscoveredModels({
serverId: input.serverId ?? "local",
provider: input.provider,
currentModels: input.currentModels,
providerSnapshotRefreshing: input.loading === true,
previousCache: input.cache ?? null,
});
}
describe("resolveProviderDiscoveredModels", () => {
it("keeps a provider's cached discovered models visible while that provider refreshes", () => {
const ready = resolveModels({ provider: "grok", currentModels: [grokModel] });
const refreshing = resolveModels({ provider: "grok", loading: true, cache: ready.cache });
expect(refreshing.models).toEqual([grokModel]);
});
it("does not show one provider's cached models while another provider loads", () => {
const ready = resolveModels({ provider: "pi", currentModels: [piModel] });
const refreshing = resolveModels({ provider: "grok", loading: true, cache: ready.cache });
expect(refreshing.models).toEqual([]);
});
it("does not show another server's cached models while the same provider loads", () => {
const ready = resolveModels({
serverId: "server-a",
provider: "grok",
currentModels: [grokModel],
});
const refreshing = resolveModels({
serverId: "server-b",
provider: "grok",
loading: true,
cache: ready.cache,
});
expect(refreshing.models).toEqual([]);
});
});

View File

@@ -0,0 +1,43 @@
import type { AgentModelDefinition } from "@getpaseo/protocol/agent-types";
export interface ProviderDiscoveredModelsCache {
serverId: string;
provider: string;
models: AgentModelDefinition[];
}
export interface ResolveProviderDiscoveredModelsInput {
serverId: string;
provider: string;
currentModels: AgentModelDefinition[] | undefined;
providerSnapshotRefreshing: boolean;
previousCache: ProviderDiscoveredModelsCache | null;
}
export interface ResolveProviderDiscoveredModelsResult {
models: AgentModelDefinition[];
cache: ProviderDiscoveredModelsCache | null;
}
export function resolveProviderDiscoveredModels({
serverId,
provider,
currentModels,
providerSnapshotRefreshing,
previousCache,
}: ResolveProviderDiscoveredModelsInput): ResolveProviderDiscoveredModelsResult {
if (currentModels && currentModels.length > 0) {
const cache = { serverId, provider, models: currentModels };
return { models: currentModels, cache };
}
if (
providerSnapshotRefreshing &&
previousCache?.serverId === serverId &&
previousCache.provider === provider
) {
return { models: previousCache.models, cache: previousCache };
}
return { models: [], cache: previousCache };
}

View File

@@ -32,6 +32,10 @@ import { formatTimeAgo } from "@/utils/time";
import { compareMatchScores, scoreTextFields } from "@/utils/score-match";
import type { AgentModelDefinition, AgentProvider } from "@getpaseo/protocol/agent-types";
import type { ProviderProfileModel } from "@getpaseo/protocol/provider-config";
import {
resolveProviderDiscoveredModels,
type ProviderDiscoveredModelsCache,
} from "./provider-diagnostic-models";
interface ProviderDiagnosticSheetProps {
provider: string;
@@ -599,21 +603,16 @@ export function ProviderDiagnosticSheet({
: null;
const modelsRefreshing = isRefreshing || providerSnapshotRefreshing;
const stableDiscoveredRef = useRef<AgentModelDefinition[]>([]);
const stableDiscoveredRef = useRef<ProviderDiscoveredModelsCache | null>(null);
const currentModels = providerEntry?.models;
if (currentModels && currentModels.length > 0) {
stableDiscoveredRef.current = currentModels;
}
const discoveredModels = useMemo(() => {
if (currentModels && currentModels.length > 0) {
return currentModels;
}
if (providerSnapshotRefreshing) {
return stableDiscoveredRef.current;
}
return [];
}, [currentModels, providerSnapshotRefreshing]);
const { models: discoveredModels, cache: nextDiscoveredCache } = resolveProviderDiscoveredModels({
serverId,
provider,
currentModels,
providerSnapshotRefreshing,
previousCache: stableDiscoveredRef.current,
});
stableDiscoveredRef.current = nextDiscoveredCache;
const [clockTick, setClockTick] = useState(0);
useEffect(() => {

View File

@@ -18,6 +18,7 @@ export function ProviderSettingsHost() {
return (
<ProviderDiagnosticSheet
key={`${serverId}:${provider}`}
provider={provider}
serverId={serverId}
visible={visible}