@@ -38,6 +38,16 @@ const modelRecordSchema = z.record(z.string(), modelInfoSchema)
3838// This prevents race conditions where multiple calls might overwrite each other's results
3939const inFlightRefresh = new Map < RouterName , Promise < ModelRecord > > ( )
4040
41+ // Providers whose model lists are scoped to the signed-in user (e.g. per-account
42+ // allowlists or org policies). For these we MUST NOT cache results on disk or
43+ // in memory: a sign-in/out cycle could otherwise serve a previous user's model
44+ // list to the next user, and stale data could mask backend allowlist updates.
45+ const AUTH_SCOPED_PROVIDERS : ReadonlySet < RouterName > = new Set ( [ "zoo-gateway" ] )
46+
47+ function isAuthScopedProvider ( provider : RouterName ) : boolean {
48+ return AUTH_SCOPED_PROVIDERS . has ( provider )
49+ }
50+
4151async function writeModels ( router : RouterName , data : ModelRecord ) {
4252 const filename = `${ router } _models.json`
4353 const cacheDir = await getCacheDirectoryPath ( ContextProxy . instance . globalStorageUri . fsPath )
@@ -124,8 +134,7 @@ async function fetchModelsFromProvider(options: GetModelsOptions): Promise<Model
124134export const getModels = async ( options : GetModelsOptions ) : Promise < ModelRecord > => {
125135 const { provider } = options
126136
127- // Always fetch fresh to prevent serving stale models from different auth contexts.
128- const shouldSkipCache = provider === "zoo-gateway"
137+ const shouldSkipCache = isAuthScopedProvider ( provider )
129138
130139 let models = shouldSkipCache ? undefined : getModelsFromCache ( provider )
131140
@@ -137,9 +146,8 @@ export const getModels = async (options: GetModelsOptions): Promise<ModelRecord>
137146 models = await fetchModelsFromProvider ( options )
138147 const modelCount = Object . keys ( models ) . length
139148
140- // Only cache non-empty results to prevent persisting failed API responses
141- // Empty results could indicate API failure rather than "no models exist"
142- // Zoo Gateway models are user-specific - skip caching entirely
149+ // Only cache non-empty results so a failed API response doesn't get persisted
150+ // as if the provider had no models. Auth-scoped providers skip caching entirely.
143151 if ( modelCount > 0 && ! shouldSkipCache ) {
144152 memoryCache . set ( provider , models )
145153
@@ -175,10 +183,7 @@ export const getModels = async (options: GetModelsOptions): Promise<ModelRecord>
175183export const refreshModels = async ( options : GetModelsOptions ) : Promise < ModelRecord > => {
176184 const { provider } = options
177185
178- // Zoo Gateway models are user-specific (auth-scoped). Mirror the bypass in
179- // getModels() so we never persist one user's model list and serve it to a
180- // different authenticated user from cache.
181- const shouldSkipCache = provider === "zoo-gateway"
186+ const shouldSkipCache = isAuthScopedProvider ( provider )
182187
183188 // Check if there's already an in-flight refresh for this provider.
184189 // This prevents race conditions where multiple concurrent refreshes might
0 commit comments