From c307b124a1c386b054ce8d9601c08899d8ee3edc Mon Sep 17 00:00:00 2001 From: Daniel Lindsley Date: Wed, 17 Dec 2025 18:09:36 -0600 Subject: [PATCH] feat: If an `apiKey` is provided, skip Oauth handling. --- packages/opencode/src/provider/provider.ts | 8 +++++ .../opencode/test/provider/provider.test.ts | 32 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index b8d4dadbd..fa34f22bf 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -640,6 +640,14 @@ export namespace Provider { const providerID = plugin.auth.provider if (disabled.has(providerID)) continue + // Skip OAuth/plugin auth if config explicitly sets an apiKey for this provider + const configProvider = config.provider?.[providerID] + const hasExplicitApiKey = configProvider?.options?.apiKey !== undefined + if (hasExplicitApiKey) { + log.debug("skipping plugin auth loader - config has explicit apiKey", { providerID }) + continue + } + // For github-copilot plugin, check if auth exists for either github-copilot or github-copilot-enterprise let hasAuth = false const auth = await Auth.get(providerID) diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index c6c6924f0..47b6ab831 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1807,3 +1807,35 @@ test("custom model inherits api.url from models.dev provider", async () => { }, }) }) + +test("config apiKey takes precedence over OAuth plugin auth loaders", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + anthropic: { + options: { + apiKey: "explicit-config-api-key", + }, + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const providers = await Provider.list() + expect(providers["anthropic"]).toBeDefined() + // When config has explicit apiKey, it should be used + // and plugin auth loaders should be skipped (no OAuth processing) + expect(providers["anthropic"].options.apiKey).toBe("explicit-config-api-key") + // Source should be "config" since config is merged last + expect(providers["anthropic"].source).toBe("config") + }, + }) +})