diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 49c582e0a99e..0201ea42f1b0 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1417,15 +1417,29 @@ export const layer = Layer.effect( if (!plugin.auth) continue const providerID = ProviderID.make(plugin.auth.provider) if (disabled.has(providerID)) continue + if (!plugin.auth.loader) continue + const data = database[plugin.auth.provider] + if (!data) continue const stored = yield* auth.get(providerID).pipe(Effect.orDie) - if (!stored) continue - if (!plugin.auth.loader) continue + const configuredKey = iife(() => { + const key = providers[providerID]?.key ?? data.options.apiKey + return typeof key === "string" && key !== "" ? key : undefined + }) + const syntheticAuth = !stored && configuredKey ? { type: "api" as const, key: configuredKey } : undefined + const pluginAuth = stored ?? syntheticAuth + if (!pluginAuth) continue const options = yield* Effect.promise(() => plugin.auth!.loader!( - () => bridge.promise(auth.get(providerID).pipe(Effect.orDie)) as any, - toPublicInfo(database[plugin.auth!.provider]), + () => + bridge.promise( + auth.get(providerID).pipe( + Effect.orDie, + Effect.map((current) => current ?? syntheticAuth), + ), + ) as any, + toPublicInfo(data), ), ) const opts = options ?? {} diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 1ea2c82424cc..4657dcb6274f 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -77,6 +77,7 @@ const languageBaseURL = (language: unknown) => (language as { config: { baseURL: const it = testEffect(Layer.mergeAll(Provider.defaultLayer, Env.defaultLayer, Plugin.defaultLayer)) const experimentalModels = testEffect(providerLayer({ enableExperimentalModels: true })) +const experimentalWebSockets = testEffect(providerLayer({ experimentalWebSockets: true })) const alphaProviderConfig = { provider: { @@ -121,6 +122,42 @@ it.instance( { config: { provider: { anthropic: { options: { apiKey: "config-api-key" } } } } }, ) +experimentalWebSockets.instance( + "openai config apiKey installs websocket fetch without stored auth", + Effect.gen(function* () { + yield* setProcessEnv("OPENCODE_AUTH_CONTENT", "{}") + const providers = yield* list + expect(providers[ProviderID.openai]).toBeDefined() + expect(providers[ProviderID.openai].options.baseURL).toBe("https://api.openai.com/v1") + expect(providers[ProviderID.openai].options.fetch).toBeFunction() + }), + { + config: { + provider: { + openai: { + options: { + baseURL: "https://api.openai.com/v1", + apiKey: "sk-test", + }, + }, + }, + }, + }, +) + +experimentalWebSockets.instance( + "openai env apiKey installs websocket fetch without stored auth", + Effect.gen(function* () { + yield* setProcessEnv("OPENCODE_AUTH_CONTENT", "{}") + yield* setProcessEnv("OPENAI_API_KEY", "test-openai-key") + const providers = yield* list + expect(providers[ProviderID.openai]).toBeDefined() + expect(providers[ProviderID.openai].source).toBe("env") + expect(providers[ProviderID.openai].key).toBe("test-openai-key") + expect(providers[ProviderID.openai].options.fetch).toBeFunction() + }), +) + it.instance( "disabled_providers excludes provider", Effect.gen(function* () {