mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
fix(core): fall back to Cloudflare environment IDs when not configured (#51963)
This commit is contained in:
@@ -1,55 +1,62 @@
|
||||
import { Effect } from "effect"
|
||||
import { isArrayNonEmpty } from "effect/Array"
|
||||
import { define } from "@opencode/plugin/effect/plugin"
|
||||
import { Form } from "@opencode/schema/form"
|
||||
import { Provider } from "../../provider.js"
|
||||
import { iife } from "../../util/iife.js"
|
||||
import { configuredSettings } from "./configured.js"
|
||||
|
||||
const providerID = Provider.ID.make("cloudflare-ai-gateway")
|
||||
|
||||
const accountIdField = Form.StringField.make({
|
||||
type: "string",
|
||||
key: "accountId",
|
||||
title: "Enter your Cloudflare Account ID",
|
||||
placeholder: "e.g. 1234567890abcdef1234567890abcdef",
|
||||
required: true,
|
||||
})
|
||||
|
||||
const gatewayIdField = Form.StringField.make({
|
||||
type: "string",
|
||||
key: "gatewayId",
|
||||
title: "Enter your Cloudflare AI Gateway ID",
|
||||
placeholder: "e.g. my-gateway",
|
||||
required: true,
|
||||
})
|
||||
|
||||
export const CloudflareAIGatewayPlugin = define({
|
||||
id: "opencode.provider.cloudflare.ai.gateway",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID
|
||||
const gatewayId = process.env.CLOUDFLARE_GATEWAY_ID
|
||||
const configured = yield* configuredSettings(providerID)
|
||||
const form = iife(() => {
|
||||
if (typeof configured?.baseURL === "string") return
|
||||
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID || stringOption(configured ?? {}, "accountId")
|
||||
const gatewayId =
|
||||
process.env.CLOUDFLARE_GATEWAY_ID ||
|
||||
stringOption(configured ?? {}, "gatewayId") ||
|
||||
stringOption(configured ?? {}, "gateway")
|
||||
if (accountId && gatewayId) return
|
||||
const accountIdForm = Form.StringField.make({
|
||||
type: "string",
|
||||
key: "accountId",
|
||||
title: "Enter your Cloudflare Account ID",
|
||||
placeholder: "e.g. 1234567890abcdef1234567890abcdef",
|
||||
required: true,
|
||||
})
|
||||
const gatewayIdForm = Form.StringField.make({
|
||||
type: "string",
|
||||
key: "gatewayId",
|
||||
title: "Enter your Cloudflare AI Gateway ID",
|
||||
placeholder: "e.g. my-gateway",
|
||||
required: true,
|
||||
})
|
||||
if (accountId) return Form.Fields.make([gatewayIdForm])
|
||||
if (gatewayId) return Form.Fields.make([accountIdForm])
|
||||
return Form.Fields.make([accountIdForm, gatewayIdForm])
|
||||
})
|
||||
const fields =
|
||||
typeof configured?.baseURL === "string"
|
||||
? []
|
||||
: [
|
||||
...(accountId || typeof configured?.accountId === "string" ? [] : [accountIdField]),
|
||||
...(gatewayId || typeof configured?.gatewayId === "string" ? [] : [gatewayIdField]),
|
||||
]
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.method.update({
|
||||
integrationID: providerID,
|
||||
method: {
|
||||
type: "key",
|
||||
label: "Gateway API token",
|
||||
form,
|
||||
form: isArrayNonEmpty(fields) ? Form.Fields.make(fields) : undefined,
|
||||
},
|
||||
})
|
||||
})
|
||||
yield* ctx.provider.transform((evt) => {
|
||||
const item = evt.get(providerID)
|
||||
if (!item || (!accountId && !gatewayId)) return
|
||||
evt.update(item.provider.id, (provider) => {
|
||||
if (typeof provider.settings?.baseURL === "string") return
|
||||
provider.settings = {
|
||||
...(accountId ? { accountId } : {}),
|
||||
...(gatewayId ? { gatewayId } : {}),
|
||||
...provider.settings,
|
||||
}
|
||||
})
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
function stringOption(options: Record<string, unknown>, key: string) {
|
||||
return typeof options[key] === "string" ? options[key] : undefined
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export const CloudflareWorkersAIPlugin = define({
|
||||
})
|
||||
|
||||
function resolveAccountId(options: Record<string, unknown>) {
|
||||
return process.env.CLOUDFLARE_ACCOUNT_ID ?? stringOption(options, "accountId")
|
||||
return stringOption(options, "accountId") ?? process.env.CLOUDFLARE_ACCOUNT_ID
|
||||
}
|
||||
|
||||
function workersEndpoint(accountId: string) {
|
||||
|
||||
@@ -4,11 +4,19 @@ import { Integration } from "@opencode/core/integration"
|
||||
import { Plugin } from "@opencode/core/plugin"
|
||||
import { PluginHost } from "@opencode/core/plugin/host"
|
||||
import { CloudflareAIGatewayPlugin } from "@opencode/core/plugin/provider/cloudflare-ai-gateway"
|
||||
import { Provider } from "@opencode/core/provider"
|
||||
import { withEnv } from "../fixture/env"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { PluginTestLayer } from "./fixture"
|
||||
|
||||
const it = testEffect(PluginTestLayer)
|
||||
const providerID = Provider.ID.make("cloudflare-ai-gateway")
|
||||
|
||||
const seed = Effect.fn(function* (fn: (provider: Provider.MutableInfo) => void) {
|
||||
const catalog = yield* Provider.Service
|
||||
yield* catalog.transform((catalog) => catalog.update(providerID, fn))
|
||||
return catalog
|
||||
})
|
||||
|
||||
const addPlugin = Effect.fn(function* () {
|
||||
const plugin = yield* Plugin.Service
|
||||
@@ -46,4 +54,55 @@ describe("CloudflareAIGatewayPlugin", () => {
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("requests only the gateway ID when the environment provides the account ID", () =>
|
||||
withEnv({ CLOUDFLARE_ACCOUNT_ID: "account", CLOUDFLARE_GATEWAY_ID: undefined }, () =>
|
||||
Effect.gen(function* () {
|
||||
yield* addPlugin()
|
||||
const integrations = yield* Integration.Service
|
||||
expect((yield* integrations.get(Integration.ID.make("cloudflare-ai-gateway")))?.methods).toContainEqual({
|
||||
type: "key",
|
||||
label: "Gateway API token",
|
||||
form: [expect.objectContaining({ type: "string", key: "gatewayId", required: true })],
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("gives the provider its environment account and gateway IDs", () =>
|
||||
withEnv({ CLOUDFLARE_ACCOUNT_ID: "account", CLOUDFLARE_GATEWAY_ID: "gateway" }, () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* seed(() => {})
|
||||
yield* addPlugin()
|
||||
expect((yield* catalog.get(providerID))?.settings).toEqual({ accountId: "account", gatewayId: "gateway" })
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("prefers configured IDs over environment IDs", () =>
|
||||
withEnv({ CLOUDFLARE_ACCOUNT_ID: "env-account", CLOUDFLARE_GATEWAY_ID: "env-gateway" }, () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* seed((provider) => {
|
||||
provider.settings = { accountId: "configured-account", gatewayId: "configured-gateway" }
|
||||
})
|
||||
yield* addPlugin()
|
||||
expect((yield* catalog.get(providerID))?.settings).toEqual({
|
||||
accountId: "configured-account",
|
||||
gatewayId: "configured-gateway",
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("leaves a configured endpoint URL untouched", () =>
|
||||
withEnv({ CLOUDFLARE_ACCOUNT_ID: "account", CLOUDFLARE_GATEWAY_ID: "gateway" }, () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* seed((provider) => {
|
||||
provider.settings = { baseURL: "https://proxy.example/v1" }
|
||||
})
|
||||
yield* addPlugin()
|
||||
expect((yield* catalog.get(providerID))?.settings).toEqual({ baseURL: "https://proxy.example/v1" })
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -119,7 +119,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("uses env account ID over configured account ID", () =>
|
||||
it.effect("uses configured account ID over env account ID", () =>
|
||||
withEnv({ CLOUDFLARE_ACCOUNT_ID: "env-acct" }, () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* seed((provider) => {
|
||||
@@ -129,7 +129,7 @@ describe("CloudflareWorkersAIPlugin", () => {
|
||||
yield* addPlugin()
|
||||
expect(required(yield* catalog.get(providerID))).toMatchObject({
|
||||
package: "aisdk:test-provider",
|
||||
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1" },
|
||||
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/configured-acct/ai/v1" },
|
||||
})
|
||||
}),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user