瀏覽代碼

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]>
MC 1 周之前
父節點
當前提交
eaa272ef7f

+ 1 - 0
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")({

+ 11 - 1
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()

+ 67 - 0
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,
+        },
+      ],
+    },
+  }
+}

+ 9 - 1
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"

+ 38 - 7
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
         })

+ 3 - 0
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 = {

+ 9 - 0
packages/sdk/openapi.json

@@ -11621,6 +11621,15 @@
           },
           "key": {
             "type": "string"
+          },
+          "metadata": {
+            "type": "object",
+            "propertyNames": {
+              "type": "string"
+            },
+            "additionalProperties": {
+              "type": "string"
+            }
           }
         },
         "required": ["type", "key"]