refactor(core): use native Cloudflare provider

This commit is contained in:
Aiden Cline
2026-08-10 17:43:42 -05:00
parent f0f72865fc
commit d40518275a
4 changed files with 89 additions and 123 deletions
@@ -0,0 +1,19 @@
import type { ProviderPackage } from "../provider-package"
import type { OpenAIProviderOptionsInput } from "./openai-options"
import { CloudflareWorkersAI } from "./cloudflare"
export interface Settings extends ProviderPackage.Settings {
readonly accountId?: string
readonly apiKey?: string
readonly providerOptions?: OpenAIProviderOptionsInput
}
export const model: ProviderPackage.Definition<Settings>["model"] = (modelID, settings) =>
CloudflareWorkersAI.configure({
...(typeof settings.baseURL === "string" ? { baseURL: settings.baseURL } : { accountId: settings.accountId ?? "" }),
apiKey: settings.apiKey,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
providerOptions: settings.providerOptions,
}).model(modelID)
@@ -0,0 +1,16 @@
import { describe, expect, test } from "bun:test"
import { model } from "../../src/providers/cloudflare-workers-ai"
describe("Cloudflare Workers AI provider package", () => {
test("derives the endpoint from accountId", () => {
const resolved = model("@cf/model", { accountId: "account", apiKey: "secret" })
expect(resolved.route.endpoint.baseURL).toBe("https://api.cloudflare.com/client/v4/accounts/account/ai/v1")
})
test("preserves an explicit endpoint", () => {
const resolved = model("@cf/model", { baseURL: "https://proxy.example/v1", apiKey: "secret" })
expect(resolved.route.endpoint.baseURL).toBe("https://proxy.example/v1")
})
})
@@ -1,22 +1,18 @@
import os from "os"
import { App } from "../../app"
import { Effect, Semaphore, Stream } from "effect"
import { Effect } from "effect"
import { define } from "@opencode-ai/plugin/effect/plugin"
import { Form } from "@opencode-ai/schema/form"
import { Bus } from "../../bus"
import { Integration } from "../../integration"
import { Provider } from "../../provider"
import { iife } from "../../util/iife"
import { configuredSettings } from "./configured"
const providerID = Provider.ID.make("cloudflare-workers-ai")
const nativePackage = "@opencode-ai/ai/providers/cloudflare-workers-ai"
export const CloudflareWorkersAIPlugin = define({
id: "opencode.provider.cloudflare-workers-ai",
effect: Effect.fn(function* (ctx) {
const bus = yield* Bus.Service
const loading = Semaphore.makeUnsafe(1)
const loaded: { accountId?: string } = {}
const configured = yield* configuredSettings(providerID)
const form = iife(() => {
if (hasExplicitEndpoint(configured?.baseURL) || resolveAccountId(configured ?? {})) return
@@ -30,14 +26,6 @@ export const CloudflareWorkersAIPlugin = define({
},
])
})
const load = Effect.fn("CloudflareWorkersAIPlugin.load")(function* () {
const connection = yield* ctx.integration.connection.active(providerID)
const credential = connection
? yield* ctx.integration.connection.resolve(connection).pipe(Effect.catch(() => Effect.succeed(undefined)))
: undefined
loaded.accountId =
credential?.type === "key" ? stringOption(credential.configuration ?? {}, "accountId") : undefined
})
yield* ctx.integration.transform((draft) => {
draft.method.update({
integrationID: providerID,
@@ -48,29 +36,25 @@ export const CloudflareWorkersAIPlugin = define({
},
})
})
yield* load()
yield* ctx.catalog.transform((evt) => {
const item = evt.provider.get(providerID)
if (!item) return
const accountId = resolveAccountId(configured ?? {}, loaded.accountId)
if (!accountId) return
const compatible =
Provider.isAISDK(item.provider.package) &&
Provider.packageName(item.provider.package) === "@ai-sdk/openai-compatible"
evt.provider.update(item.provider.id, (provider) => {
if (!Provider.isAISDK(provider.package)) return
const baseURL = provider.settings?.baseURL
if (hasExplicitEndpoint(baseURL)) return
provider.settings = {
...provider.settings,
baseURL: typeof baseURL === "string" ? expandAccountId(baseURL, accountId) : workersEndpoint(accountId),
}
if (!compatible) return
provider.package = nativePackage
provider.settings = nativeSettings(provider.settings)
})
for (const model of item.models.values()) {
if (typeof model.settings?.baseURL !== "string") continue
const modelAccountId = resolveAccountId(model.settings, accountId)
evt.model.update(item.provider.id, model.id, (draft) => {
draft.settings = {
...draft.settings,
baseURL: expandAccountId(draft.settings?.baseURL, modelAccountId),
}
if (!draft.package && !compatible) return
if (draft.package === nativePackage) return
if (draft.package && !Provider.isAISDK(draft.package)) return
if (draft.package && Provider.packageName(draft.package) !== "@ai-sdk/openai-compatible") return
if (draft.package) draft.package = nativePackage
draft.settings = nativeSettings(draft.settings)
})
}
})
@@ -101,17 +85,11 @@ export const CloudflareWorkersAIPlugin = define({
evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id)
}),
)
const refresh = () => loading.withPermit(load().pipe(Effect.andThen(ctx.catalog.reload())))
yield* bus.subscribe(Integration.Event.ConnectionUpdated).pipe(
Stream.filter((event) => event.data.integrationID === Integration.ID.make(providerID)),
Stream.runForEach(refresh),
Effect.forkScoped({ startImmediately: true }),
)
}),
})
function resolveAccountId(options: Record<string, unknown>, connected?: string) {
return process.env.CLOUDFLARE_ACCOUNT_ID ?? stringOption(options, "accountId") ?? connected
function resolveAccountId(options: Record<string, unknown>) {
return process.env.CLOUDFLARE_ACCOUNT_ID ?? stringOption(options, "accountId")
}
function workersEndpoint(accountId: string) {
@@ -122,6 +100,13 @@ function hasExplicitEndpoint(baseURL: unknown) {
return typeof baseURL === "string" && !baseURL.includes("${CLOUDFLARE_ACCOUNT_ID}")
}
function nativeSettings(settings: Record<string, unknown> | undefined) {
const result = { ...settings }
if (process.env.CLOUDFLARE_ACCOUNT_ID) result.baseURL = workersEndpoint(process.env.CLOUDFLARE_ACCOUNT_ID)
else if (!hasExplicitEndpoint(result.baseURL)) delete result.baseURL
return result
}
function hasWorkersEndpoint(model: {
readonly package?: string
readonly settings?: Readonly<Record<string, unknown>>
@@ -1,10 +1,9 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect, Fiber, Stream } from "effect"
import { TestClock } from "effect/testing"
import { Bus } from "@opencode-ai/core/bus"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Credential } from "@opencode-ai/core/credential"
import { ModelResolver } from "@opencode-ai/core/model-resolver"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
@@ -19,7 +18,6 @@ const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* CloudflareWorkersAIPlugin.effect(host)
})
@@ -106,15 +104,13 @@ describe("CloudflareWorkersAIPlugin", () => {
),
)
it.effect("maps account ID to endpoint URL and creates an OpenAI-compatible SDK", () =>
it.effect("maps the environment account ID to the native endpoint", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(Provider.ID.make("cloudflare-workers-ai"), (provider) => {
provider.package = Provider.aisdk("test-provider")
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
}),
)
yield* addPlugin()
@@ -122,21 +118,10 @@ describe("CloudflareWorkersAIPlugin", () => {
(yield* (yield* Integration.Service).get(Integration.ID.make("cloudflare-workers-ai")))?.methods,
).toContainEqual({ type: "key", label: "API key" })
const provider = required(yield* catalog.provider.get(Provider.ID.make("cloudflare-workers-ai")))
const sdk = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: provider.package,
settings: provider.settings,
}),
package: "@ai-sdk/openai-compatible",
options: { name: "cloudflare-workers-ai", headers: { custom: "header" } },
})
expect(provider).toMatchObject({
package: "aisdk:test-provider",
package: "@opencode-ai/ai/providers/cloudflare-workers-ai",
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1" },
})
expect(sdk.sdk).toBeDefined()
}),
),
)
@@ -196,30 +181,32 @@ describe("CloudflareWorkersAIPlugin", () => {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(Provider.ID.make("cloudflare-workers-ai"), (provider) => {
provider.package = Provider.aisdk("test-provider")
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { ...provider.settings, accountId: "configured-acct" }
}),
)
yield* addPlugin()
expect(required(yield* catalog.provider.get(Provider.ID.make("cloudflare-workers-ai")))).toMatchObject({
package: "aisdk:test-provider",
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1" },
package: "@opencode-ai/ai/providers/cloudflare-workers-ai",
settings: {
accountId: "configured-acct",
baseURL: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1",
},
})
}),
),
)
it.effect("reloads provider and model endpoints from a connected account ID", () =>
it.effect("passes the connected account ID to the native provider at runtime", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: undefined }, () =>
Effect.gen(function* () {
const bus = yield* Bus.Service
const catalog = yield* Catalog.Service
const integrations = yield* Integration.Service
const providerID = Provider.ID.make("cloudflare-workers-ai")
yield* catalog.transform((draft) => {
draft.provider.update(providerID, (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = {
accountId: "configured-acct",
baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
}
})
@@ -232,70 +219,29 @@ describe("CloudflareWorkersAIPlugin", () => {
})
yield* addPlugin()
const updated = yield* bus
.subscribe(Catalog.Event.Updated)
.pipe(Stream.take(1), Stream.runHead, Effect.forkScoped({ startImmediately: true }))
yield* Effect.yieldNow
yield* integrations.connection.key({
integrationID: Integration.ID.make(providerID),
key: "secret",
answer: { accountId: "connected-acct" },
})
yield* Effect.yieldNow
yield* TestClock.adjust(500)
yield* Fiber.join(updated)
expect(required(yield* catalog.provider.get(providerID)).settings?.baseURL).toBe(
"https://api.cloudflare.com/client/v4/accounts/connected-acct/ai/v1",
)
expect(required(yield* catalog.model.get(providerID, Model.ID.make("@cf/model"))).settings?.baseURL).toBe(
"https://api.cloudflare.com/client/v4/accounts/model-acct/ai/v1",
)
}),
),
)
it.effect("loads a connected account at startup and restores the template after removal", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: undefined }, () =>
Effect.gen(function* () {
const bus = yield* Bus.Service
const catalog = yield* Catalog.Service
const credentials = yield* Credential.Service
const integrations = yield* Integration.Service
const providerID = Provider.ID.make("cloudflare-workers-ai")
yield* catalog.transform((draft) =>
draft.provider.update(providerID, (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = {
baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
}
}),
)
const credential = yield* credentials.create({
integrationID: Integration.ID.make(providerID),
value: Credential.Key.make({
const selected = required(yield* catalog.model.get(providerID, Model.ID.make("@cf/model")))
const { model } = yield* Effect.promise(() => import("@opencode-ai/ai/providers/cloudflare-workers-ai"))
const resolved = yield* ModelResolver.fromCatalogModel(
selected,
Credential.Key.make({
type: "key",
key: "secret",
configuration: { accountId: "startup-acct" },
configuration: { accountId: "connected-acct" },
}),
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(providerID)).settings?.baseURL).toBe(
"https://api.cloudflare.com/client/v4/accounts/startup-acct/ai/v1",
{ loadPackage: () => Effect.succeed({ model }) },
)
const updated = yield* bus
.subscribe(Catalog.Event.Updated)
.pipe(Stream.take(1), Stream.runHead, Effect.forkScoped({ startImmediately: true }))
yield* Effect.yieldNow
yield* integrations.connection.remove(credential.id)
yield* Effect.yieldNow
yield* TestClock.adjust(500)
yield* Fiber.join(updated)
expect(required(yield* catalog.provider.get(providerID)).settings?.baseURL).toBe(
"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
expect(required(yield* catalog.provider.get(providerID))).toMatchObject({
package: "@opencode-ai/ai/providers/cloudflare-workers-ai",
settings: { accountId: "configured-acct" },
})
expect(selected).toMatchObject({
package: "@opencode-ai/ai/providers/cloudflare-workers-ai",
settings: { accountId: "model-acct" },
})
expect(selected.settings).not.toHaveProperty("baseURL")
expect(resolved.route.endpoint.baseURL).toBe(
"https://api.cloudflare.com/client/v4/accounts/connected-acct/ai/v1",
)
}),
),