Skip to content

Commit 45b6ed2

Browse files
refactor(zoo-gateway): centralize auth-scoped cache-skip via AUTH_SCOPED_PROVIDERS
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 24b8ffe commit 45b6ed2

1 file changed

Lines changed: 14 additions & 9 deletions

File tree

src/api/providers/fetchers/modelCache.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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
3939
const 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+
4151
async 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
124134
export 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>
175183
export 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

Comments
 (0)