diff --git a/src/oauth/index.ts b/src/oauth/index.ts index 778ba33069..777f1e1870 100644 --- a/src/oauth/index.ts +++ b/src/oauth/index.ts @@ -777,6 +777,11 @@ function migrateLegacyAntigravityStaticCatalog(config: OcxConfig): boolean { return true; } +/** An explicit static catalog is operator-owned and must not be replaced by OAuth presets. */ +function isOperatorOwnedStaticCatalog(provider: OcxProviderConfig): boolean { + return provider.liveModels === false; +} + export function reconcileOAuthProviders(config: OcxConfig): boolean { let changed = migrateLegacyAntigravityStaticCatalog(config); for (const [name, prov] of Object.entries(config.providers)) { @@ -788,6 +793,7 @@ export function reconcileOAuthProviders(config: OcxConfig): boolean { changed = true; } if (!def || prov.authMode !== "oauth") continue; + if (isOperatorOwnedStaticCatalog(prov)) continue; const preset = def.providerConfig; for (const field of OAUTH_RECONCILE_FIELDS) { if (JSON.stringify(prov[field]) === JSON.stringify(preset[field])) continue; diff --git a/tests/oauth-provider-reconcile.test.ts b/tests/oauth-provider-reconcile.test.ts index 611a4e969f..a52a42d26f 100644 --- a/tests/oauth-provider-reconcile.test.ts +++ b/tests/oauth-provider-reconcile.test.ts @@ -116,6 +116,34 @@ describe("OAuth provider reconciliation", () => { expect(config.providers["google-antigravity"].liveModels).toBe(false); }); + test("preserves operator-owned static OAuth catalog fields", () => { + const config = { + port: 10100, + defaultProvider: "command-code", + providers: { + "command-code": { + ...structuredClone(OAUTH_PROVIDERS["command-code"].providerConfig), + liveModels: false, + models: ["deepseek/deepseek-v4-flash", "xiaomi/mimo-v2.5-pro"], + modelContextWindows: { + "deepseek/deepseek-v4-flash": 1_000_000, + "xiaomi/mimo-v2.5-pro": 1_050_000, + }, + }, + }, + } satisfies OcxConfig; + + expect(reconcileOAuthProviders(config)).toBe(false); + expect(config.providers["command-code"].models).toEqual([ + "deepseek/deepseek-v4-flash", + "xiaomi/mimo-v2.5-pro", + ]); + expect(config.providers["command-code"].modelContextWindows).toEqual({ + "deepseek/deepseek-v4-flash": 1_000_000, + "xiaomi/mimo-v2.5-pro": 1_050_000, + }); + }); + test("preserves explicit Antigravity live discovery when authMode is omitted or non-OAuth", () => { const home = mkdtempSync(join(tmpdir(), "ocx-antigravity-authmode-reconcile-")); homes.push(home);