From 7cf4981ef7bc986844672ffa472d2197d575e14d Mon Sep 17 00:00:00 2001 From: joshavant <830519+joshavant@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:20:04 -0500 Subject: [PATCH] fix(models): complete full catalog auth refresh --- src/agents/auth-profiles/external-cli-sync.ts | 5 ++++ ...d-model-catalog-worker.integration.test.ts | 30 ++++++++++++++----- src/agents/prepared-model-catalog-worker.ts | 2 +- src/agents/prepared-model-catalog.test.ts | 11 +++++-- src/agents/prepared-model-catalog.worker.ts | 2 ++ src/agents/prepared-model-runtime-auth.ts | 12 -------- .../chat-metadata-runtime.test.ts | 5 ---- 7 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/agents/auth-profiles/external-cli-sync.ts b/src/agents/auth-profiles/external-cli-sync.ts index 991408e7700c..48db41611195 100644 --- a/src/agents/auth-profiles/external-cli-sync.ts +++ b/src/agents/auth-profiles/external-cli-sync.ts @@ -132,6 +132,11 @@ function listExternalCliProviderIds(providerConfig: ExternalCliSyncProvider): st return [providerConfig.provider, ...(providerConfig.aliases ?? [])]; } +/** Provider ids whose external CLI credentials can be refreshed by this owner. */ +export function listExternalCliSyncProviderIds(): string[] { + return [...new Set(EXTERNAL_CLI_SYNC_PROVIDERS.flatMap(listExternalCliProviderIds))]; +} + function normalizeExternalCliCredentialProvider( credential: OAuthCredential | null, provider: string, diff --git a/src/agents/prepared-model-catalog-worker.integration.test.ts b/src/agents/prepared-model-catalog-worker.integration.test.ts index 8d2aec2c953f..bdb41aa2b135 100644 --- a/src/agents/prepared-model-catalog-worker.integration.test.ts +++ b/src/agents/prepared-model-catalog-worker.integration.test.ts @@ -22,7 +22,6 @@ import { getPreparedModelFullCatalogAuth, runPreparedModelCatalogWorker, } from "./prepared-model-catalog-worker.js"; -import { copyPreparedModelRuntimeAuthState } from "./prepared-model-runtime-auth.js"; import { startSerializedSnapshotBuild } from "./prepared-model-runtime.build.js"; import type { PreparedModelRuntimeAgentFacts } from "./prepared-model-runtime.facts.js"; import { AuthStorage } from "./sessions/auth-storage.js"; @@ -281,12 +280,21 @@ describe("prepared model catalog worker boundary", () => { config, modelCatalog: { entries: [route], routeVariants: [route] }, }); - copyPreparedModelRuntimeAuthState(fixture.snapshot, owner); - const project = async () => - await loadGatewayModelCatalogSnapshot({ + const project = async () => { + const fullCatalog = await fixture.snapshot.loadFullModelCatalog?.(); + const fullAuth = fullCatalog && getPreparedModelFullCatalogAuth(fullCatalog); + if (!fullAuth) { + throw new Error("full catalog omitted prepared auth"); + } + return await loadGatewayModelCatalogSnapshot({ getConfig: () => config, - loadPublishedPreparedModelCatalogOwnerSnapshot: async () => owner, + loadPublishedPreparedModelCatalogOwnerSnapshot: async () => ({ + ...owner, + authModes: fullAuth.authModes, + authStore: fullAuth.authStore, + }), }); + }; const projectModels = async () => { const projected = await project(); const context = { @@ -388,11 +396,19 @@ describe("prepared model catalog worker boundary", () => { config, modelCatalog: { entries: [route], routeVariants: [route] }, }); - copyPreparedModelRuntimeAuthState(fixture.snapshot, owner); const listModels = async () => { + const fullCatalog = await fixture.snapshot.loadFullModelCatalog?.(); + const fullAuth = fullCatalog && getPreparedModelFullCatalogAuth(fullCatalog); + if (!fullAuth) { + throw new Error("full catalog omitted prepared auth"); + } const projected = await loadGatewayModelCatalogSnapshot({ getConfig: () => config, - loadPublishedPreparedModelCatalogOwnerSnapshot: async () => owner, + loadPublishedPreparedModelCatalogOwnerSnapshot: async () => ({ + ...owner, + authModes: fullAuth.authModes, + authStore: fullAuth.authStore, + }), }); const context = { getRuntimeConfig: () => config, diff --git a/src/agents/prepared-model-catalog-worker.ts b/src/agents/prepared-model-catalog-worker.ts index b231fa8a4f0e..574a5f6a09fa 100644 --- a/src/agents/prepared-model-catalog-worker.ts +++ b/src/agents/prepared-model-catalog-worker.ts @@ -56,7 +56,7 @@ const authByFullCatalog = new WeakMap< Readonly<{ authStore: AuthProfileStore; authModes: PreparedAgentCredentialModes }> >(); -export function setPreparedModelFullCatalogAuth( +function setPreparedModelFullCatalogAuth( modelCatalog: object, auth: Readonly<{ authStore: AuthProfileStore; authModes: PreparedAgentCredentialModes }>, ): void { diff --git a/src/agents/prepared-model-catalog.test.ts b/src/agents/prepared-model-catalog.test.ts index 2cea9647ac7b..80bbe507fc6a 100644 --- a/src/agents/prepared-model-catalog.test.ts +++ b/src/agents/prepared-model-catalog.test.ts @@ -10,6 +10,9 @@ const mocks = vi.hoisted(() => ({ loadSnapshot: vi.fn(), prepareSnapshot: vi.fn(), prepareScopedCatalog: vi.fn(), + fullCatalogAuth: undefined as + | undefined + | { authStore: { version: number; profiles: object }; authModes: object }, isFullCatalog: vi.fn(), releaseSnapshot: vi.fn(), })); @@ -53,11 +56,14 @@ vi.mock("./prepared-model-runtime.facts.js", () => ({ isPreparedModelCatalogFull: (...args: unknown[]) => mocks.isFullCatalog(...args), })); +vi.mock("./prepared-model-catalog-worker.js", () => ({ + getPreparedModelFullCatalogAuth: () => mocks.fullCatalogAuth, +})); + vi.mock("./prepared-model-runtime.scoped-catalog.js", () => ({ prepareScopedReadOnlyModelCatalog: (...args: unknown[]) => mocks.prepareScopedCatalog(...args), })); -import { setPreparedModelFullCatalogAuth } from "./prepared-model-catalog-worker.js"; import { PreparedModelCatalogConfigReplacedError } from "./prepared-model-catalog.errors.js"; import { getPublishedPreparedModelCatalogOwnerSnapshot, @@ -99,6 +105,7 @@ describe("prepared model catalog access", () => { mocks.loadSnapshot.mockReset(); mocks.prepareSnapshot.mockReset(); mocks.prepareScopedCatalog.mockReset(); + mocks.fullCatalogAuth = undefined; mocks.isFullCatalog.mockReset(); mocks.releaseSnapshot.mockReset(); }); @@ -200,7 +207,7 @@ describe("prepared model catalog access", () => { routeVariants: [], }; const { authStore, ...snapshotFacts } = fullSnapshot; - setPreparedModelFullCatalogAuth(discoveredCatalog, { authStore, authModes: {} }); + mocks.fullCatalogAuth = { authStore, authModes: {} }; const loadFullModelCatalog = vi.fn(async () => discoveredCatalog); const snapshot = { ...snapshotFacts, diff --git a/src/agents/prepared-model-catalog.worker.ts b/src/agents/prepared-model-catalog.worker.ts index 24bdd3b7534b..0c00a2a8c780 100644 --- a/src/agents/prepared-model-catalog.worker.ts +++ b/src/agents/prepared-model-catalog.worker.ts @@ -7,6 +7,7 @@ import { } from "./agent-auth-credentials.js"; import { resolveAmbientAgentCredentialsForDiscovery } from "./agent-auth-discovery.js"; import { overlayExternalAuthProfiles } from "./auth-profiles/external-auth.js"; +import { listExternalCliSyncProviderIds } from "./auth-profiles/external-cli-sync.js"; import { replaceRuntimeAuthProfileStoreSnapshots } from "./auth-profiles/runtime-snapshots.js"; import { loadAuthProfileStoreWithoutExternalProfiles, @@ -98,6 +99,7 @@ export async function runPreparedModelCatalogWorkerInput( authStore: value.authStore, config: value.input.config, env: value.input.env ?? process.env, + providerIds: listExternalCliSyncProviderIds(), }); replaceRuntimeAuthProfileStoreSnapshots([{ agentDir: value.input.agentDir, store: authStore }]); const ambientCredentials = withPluginRuntimeRegistryScope( diff --git a/src/agents/prepared-model-runtime-auth.ts b/src/agents/prepared-model-runtime-auth.ts index e2870181d791..80cc6e8ad75b 100644 --- a/src/agents/prepared-model-runtime-auth.ts +++ b/src/agents/prepared-model-runtime-auth.ts @@ -48,15 +48,3 @@ export function getPreparedModelRuntimeAuthMaterializations( ): readonly RuntimeAuthMaterialization[] { return materializationsBySnapshot.get(snapshot) ?? []; } - -export function copyPreparedModelRuntimeAuthState(source: object, target: object): void { - const authStore = authStoreBySnapshot.get(source); - if (authStore) { - authStoreBySnapshot.set(target, authStore); - } - const authStoreLoader = authStoreLoaderBySnapshot.get(source); - if (authStoreLoader) { - authStoreLoaderBySnapshot.set(target, authStoreLoader); - } - materializationsBySnapshot.set(target, getPreparedModelRuntimeAuthMaterializations(source)); -} diff --git a/src/gateway/server-methods/chat-metadata-runtime.test.ts b/src/gateway/server-methods/chat-metadata-runtime.test.ts index b7f991b523f1..cb24a2807041 100644 --- a/src/gateway/server-methods/chat-metadata-runtime.test.ts +++ b/src/gateway/server-methods/chat-metadata-runtime.test.ts @@ -7,7 +7,6 @@ import { } from "../../agents/agent-auth-credentials.js"; import type { AuthProfileStore } from "../../agents/auth-profiles.js"; import type { ModelCatalogEntry } from "../../agents/model-catalog.types.js"; -import { setPreparedModelFullCatalogAuth } from "../../agents/prepared-model-catalog-worker.js"; import { getPreparedModelRuntimeAuthStore, setPreparedModelRuntimeAuthStore, @@ -498,10 +497,6 @@ describe("gateway chat metadata runtime", () => { ...owner.modelCatalog, providerOutcomes: [{ provider: "openai", status: "auth-rejected" as const }], }; - setPreparedModelFullCatalogAuth(fullCatalog, { - authStore: getPreparedModelRuntimeAuthStore(owner)!, - authModes: owner.authModes, - }); const loadFullModelCatalog = vi.fn(async () => fullCatalog); harness.setOwner({ ...owner,