summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorMC <[email protected]>2026-04-06 01:26:04 -0400
committerGitHub <[email protected]>2026-04-06 05:26:04 +0000
commiteaa272ef7f034137746d2ed5d13383d9ef20ca8d (patch)
treee8037b4868cb4cefc5e248429bbf32304e4a0192 /packages
parent70b636a360517ddb91658eff8ce0c2bbde45cb9f (diff)
downloadopencode-eaa272ef7f034137746d2ed5d13383d9ef20ca8d.tar.gz
opencode-eaa272ef7f034137746d2ed5d13383d9ef20ca8d.zip
fix: show clear error when Cloudflare provider env vars are missing (#20399)
Co-authored-by: Aiden Cline <[email protected]> Co-authored-by: Aiden Cline <[email protected]>
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/auth/index.ts1
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx12
-rw-r--r--packages/opencode/src/plugin/cloudflare.ts67
-rw-r--r--packages/opencode/src/plugin/index.ts10
-rw-r--r--packages/opencode/src/provider/provider.ts45
-rw-r--r--packages/sdk/js/src/v2/gen/types.gen.ts3
-rw-r--r--packages/sdk/openapi.json9
7 files changed, 138 insertions, 9 deletions
diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts
index b6d340cc8..2a9fb6c19 100644
--- a/packages/opencode/src/auth/index.ts
+++ b/packages/opencode/src/auth/index.ts
@@ -24,6 +24,7 @@ export namespace Auth {
export class Api extends Schema.Class<Api>("ApiAuth")({
type: Schema.Literal("api"),
key: Schema.String,
+ metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)),
}) {}
export class WellKnown extends Schema.Class<WellKnown>("WellKnownAuth")({
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
index 8add73dd6..cb7abb822 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
@@ -129,7 +129,15 @@ export function createDialogProviderOptions() {
}
}
if (method.type === "api") {
- return dialog.replace(() => <ApiMethod providerID={provider.id} title={method.label} />)
+ let metadata: Record<string, string> | undefined
+ if (method.prompts?.length) {
+ const value = await PromptsMethod({ dialog, prompts: method.prompts })
+ if (!value) return
+ metadata = value
+ }
+ return dialog.replace(() => (
+ <ApiMethod providerID={provider.id} title={method.label} metadata={metadata} />
+ ))
}
},
}
@@ -249,6 +257,7 @@ function CodeMethod(props: CodeMethodProps) {
interface ApiMethodProps {
providerID: string
title: string
+ metadata?: Record<string, string>
}
function ApiMethod(props: ApiMethodProps) {
const dialog = useDialog()
@@ -293,6 +302,7 @@ function ApiMethod(props: ApiMethodProps) {
auth: {
type: "api",
key: value,
+ ...(props.metadata ? { metadata: props.metadata } : {}),
},
})
await sdk.client.instance.dispose()
diff --git a/packages/opencode/src/plugin/cloudflare.ts b/packages/opencode/src/plugin/cloudflare.ts
new file mode 100644
index 000000000..e20a488a3
--- /dev/null
+++ b/packages/opencode/src/plugin/cloudflare.ts
@@ -0,0 +1,67 @@
+import type { Hooks, PluginInput } from "@opencode-ai/plugin"
+
+export async function CloudflareWorkersAuthPlugin(_input: PluginInput): Promise<Hooks> {
+ const prompts = [
+ ...(!process.env.CLOUDFLARE_ACCOUNT_ID
+ ? [
+ {
+ type: "text" as const,
+ key: "accountId",
+ message: "Enter your Cloudflare Account ID",
+ placeholder: "e.g. 1234567890abcdef1234567890abcdef",
+ },
+ ]
+ : []),
+ ]
+
+ return {
+ auth: {
+ provider: "cloudflare-workers-ai",
+ methods: [
+ {
+ type: "api",
+ label: "API key",
+ prompts,
+ },
+ ],
+ },
+ }
+}
+
+export async function CloudflareAIGatewayAuthPlugin(_input: PluginInput): Promise<Hooks> {
+ const prompts = [
+ ...(!process.env.CLOUDFLARE_ACCOUNT_ID
+ ? [
+ {
+ type: "text" as const,
+ key: "accountId",
+ message: "Enter your Cloudflare Account ID",
+ placeholder: "e.g. 1234567890abcdef1234567890abcdef",
+ },
+ ]
+ : []),
+ ...(!process.env.CLOUDFLARE_GATEWAY_ID
+ ? [
+ {
+ type: "text" as const,
+ key: "gatewayId",
+ message: "Enter your Cloudflare AI Gateway ID",
+ placeholder: "e.g. my-gateway",
+ },
+ ]
+ : []),
+ ]
+
+ return {
+ auth: {
+ provider: "cloudflare-ai-gateway",
+ methods: [
+ {
+ type: "api",
+ label: "Gateway API token",
+ prompts,
+ },
+ ],
+ },
+ }
+}
diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts
index fb60fa096..df69c8eba 100644
--- a/packages/opencode/src/plugin/index.ts
+++ b/packages/opencode/src/plugin/index.ts
@@ -10,6 +10,7 @@ import { NamedError } from "@opencode-ai/util/error"
import { CopilotAuthPlugin } from "./github-copilot/copilot"
import { gitlabAuthPlugin as GitlabAuthPlugin } from "opencode-gitlab-auth"
import { PoeAuthPlugin } from "opencode-poe-auth"
+import { CloudflareAIGatewayAuthPlugin, CloudflareWorkersAuthPlugin } from "./cloudflare"
import { Effect, Layer, ServiceMap, Stream } from "effect"
import { InstanceState } from "@/effect/instance-state"
import { makeRuntime } from "@/effect/run-service"
@@ -46,7 +47,14 @@ export namespace Plugin {
export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/Plugin") {}
// Built-in plugins that are directly imported (not installed from npm)
- const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin, CopilotAuthPlugin, GitlabAuthPlugin, PoeAuthPlugin]
+ const INTERNAL_PLUGINS: PluginInstance[] = [
+ CodexAuthPlugin,
+ CopilotAuthPlugin,
+ GitlabAuthPlugin,
+ PoeAuthPlugin,
+ CloudflareWorkersAuthPlugin,
+ CloudflareAIGatewayAuthPlugin,
+ ]
function isServerPlugin(value: unknown): value is PluginInstance {
return typeof value === "function"
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 924d13312..9ca49bf8f 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -672,13 +672,26 @@ export namespace Provider {
}
}),
"cloudflare-workers-ai": Effect.fnUntraced(function* (input: Info) {
- const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID")
- if (!accountId) return { autoload: false }
+ // When baseURL is already configured (e.g. corporate config routing through a proxy/gateway),
+ // skip the account ID check because the URL is already fully specified.
+ if (input.options?.baseURL) return { autoload: false }
+
+ const auth = yield* dep.auth(input.id)
+ const accountId =
+ Env.get("CLOUDFLARE_ACCOUNT_ID") || (auth?.type === "api" ? auth.metadata?.accountId : undefined)
+ if (!accountId)
+ return {
+ autoload: false,
+ async getModel() {
+ throw new Error(
+ "CLOUDFLARE_ACCOUNT_ID is missing. Set it with: export CLOUDFLARE_ACCOUNT_ID=<your-account-id>",
+ )
+ },
+ }
const apiKey = yield* Effect.gen(function* () {
const envToken = Env.get("CLOUDFLARE_API_KEY")
if (envToken) return envToken
- const auth = yield* dep.auth(input.id)
if (auth?.type === "api") return auth.key
return undefined
})
@@ -702,16 +715,34 @@ export namespace Provider {
}
}),
"cloudflare-ai-gateway": Effect.fnUntraced(function* (input: Info) {
- const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID")
- const gateway = Env.get("CLOUDFLARE_GATEWAY_ID")
+ // When baseURL is already configured (e.g. corporate config), skip the ID checks.
+ if (input.options?.baseURL) return { autoload: false }
- if (!accountId || !gateway) return { autoload: false }
+ const auth = yield* dep.auth(input.id)
+ const accountId =
+ Env.get("CLOUDFLARE_ACCOUNT_ID") || (auth?.type === "api" ? auth.metadata?.accountId : undefined)
+ const gateway =
+ Env.get("CLOUDFLARE_GATEWAY_ID") || (auth?.type === "api" ? auth.metadata?.gatewayId : undefined)
+
+ if (!accountId || !gateway) {
+ const missing = [
+ !accountId ? "CLOUDFLARE_ACCOUNT_ID" : undefined,
+ !gateway ? "CLOUDFLARE_GATEWAY_ID" : undefined,
+ ].filter((x): x is string => Boolean(x))
+ return {
+ autoload: false,
+ async getModel() {
+ throw new Error(
+ `${missing.join(" and ")} missing. Set with: ${missing.map((x) => `export ${x}=<value>`).join(" && ")}`,
+ )
+ },
+ }
+ }
// Get API token from env or auth - required for authenticated gateways
const apiToken = yield* Effect.gen(function* () {
const envToken = Env.get("CLOUDFLARE_API_TOKEN") || Env.get("CF_AIG_TOKEN")
if (envToken) return envToken
- const auth = yield* dep.auth(input.id)
if (auth?.type === "api") return auth.key
return undefined
})
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index 72e549e48..548ab8363 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -1639,6 +1639,9 @@ export type OAuth = {
export type ApiAuth = {
type: "api"
key: string
+ metadata?: {
+ [key: string]: string
+ }
}
export type WellKnownAuth = {
diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json
index 1aa4010e7..e21c48e89 100644
--- a/packages/sdk/openapi.json
+++ b/packages/sdk/openapi.json
@@ -11621,6 +11621,15 @@
},
"key": {
"type": "string"
+ },
+ "metadata": {
+ "type": "object",
+ "propertyNames": {
+ "type": "string"
+ },
+ "additionalProperties": {
+ "type": "string"
+ }
}
},
"required": ["type", "key"]