Просмотр исходного кода

refactor(core): route Workers AI natively

Aiden Cline 1 неделя назад
Родитель
Сommit
478d0bd533

+ 26 - 0
packages/ai/src/providers/cloudflare.ts

@@ -2,6 +2,7 @@ import type { Config, Redacted } from "effect"
 import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
 import { Auth } from "../route/auth"
 import { AuthOptions, type AtLeastOne, type ProviderAuthOption } from "../route/auth-options"
+import type { ProviderPackage } from "../provider-package"
 import type { RouteDefaultsInput } from "../route/client"
 import { ProviderID, type ModelID } from "../schema"
 import type { OpenAIProviderOptionsInput } from "./openai-options"
@@ -39,6 +40,13 @@ export type WorkersAIOptions = WorkersAIURL &
     readonly providerOptions?: OpenAIProviderOptionsInput
   }
 
+export interface WorkersAISettings extends ProviderPackage.Settings {
+  readonly accountId?: string
+  readonly apiKey?: string
+  readonly baseURL?: string
+  readonly providerOptions?: OpenAIProviderOptionsInput
+}
+
 export const aiGatewayBaseURL = (input: GatewayURL) => {
   if (input.baseURL) return input.baseURL
   if (!input.accountId) throw new Error("CloudflareAIGateway.configure requires accountId unless baseURL is supplied")
@@ -131,3 +139,21 @@ export const CloudflareWorkersAI = {
   id: workersAIID,
   configure: configureWorkersAI,
 }
+
+export const workersAIModel: ProviderPackage.Definition<WorkersAISettings, OpenAIProviderOptionsInput>["model"] = (
+  modelID,
+  settings,
+) => {
+  const body = settings.body === undefined ? undefined : { ...settings.body }
+  if (body) delete body.accountId
+  const defaults = {
+    apiKey: settings.apiKey,
+    headers: settings.headers === undefined ? undefined : { ...settings.headers },
+    http: body === undefined ? undefined : { body },
+    limits: settings.limits,
+    providerOptions: settings.providerOptions,
+  }
+  if (settings.baseURL) return configureWorkersAI({ ...defaults, baseURL: settings.baseURL }).model(modelID)
+  if (settings.accountId) return configureWorkersAI({ ...defaults, accountId: settings.accountId }).model(modelID)
+  throw new Error("Cloudflare Workers AI requires accountId or baseURL")
+}

+ 2 - 0
packages/ai/src/providers/cloudflare/workers-ai.ts

@@ -0,0 +1,2 @@
+export { workersAIModel as model } from "../cloudflare"
+export type { WorkersAISettings as Settings } from "../cloudflare"

+ 20 - 0
packages/ai/test/provider-package.test.ts

@@ -26,6 +26,7 @@ describe("provider package entrypoints", () => {
       import("@opencode-ai/ai/providers/amazon-bedrock/mantle"),
       import("@opencode-ai/ai/providers/amazon-bedrock/mantle/chat"),
       import("@opencode-ai/ai/providers/amazon-bedrock/mantle/responses"),
+      import("@opencode-ai/ai/providers/cloudflare/workers-ai"),
     ])
 
     for (const module of modules) expect(module.model).toBeFunction()
@@ -35,6 +36,25 @@ describe("provider package entrypoints", () => {
     expect(modules[19].model).toBe(modules[20].model)
   })
 
+  test("maps Cloudflare Workers AI settings onto its native route", async () => {
+    const WorkersAI = await import("@opencode-ai/ai/providers/cloudflare/workers-ai")
+    const model = WorkersAI.model("@cf/meta/llama-3.1-8b-instruct", {
+      accountId: "account/id",
+      apiKey: "secret",
+      body: { custom: true, accountId: "account/id" },
+      limits: { context: 128_000, output: 8_192 },
+    })
+
+    expect(model.route).toMatchObject({
+      id: "cloudflare-workers-ai",
+      endpoint: { baseURL: "https://api.cloudflare.com/client/v4/accounts/account%2Fid/ai/v1" },
+      defaults: {
+        http: { body: { custom: true } },
+        limits: { context: 128_000, output: 8_192 },
+      },
+    })
+  })
+
   test("maps OpenRouter and xAI package settings onto executable models", async () => {
     const OpenRouter = await import("@opencode-ai/ai/providers/openrouter")
     const XAI = await import("@opencode-ai/ai/providers/xai")

+ 46 - 0
packages/core/src/aisdk-native.ts

@@ -12,6 +12,7 @@ export interface Mapping {
 
 export interface MapInput {
   readonly packageName: string | undefined
+  readonly providerID: string
   readonly settings: Readonly<Record<string, unknown>>
   readonly modelID: string
 }
@@ -51,6 +52,10 @@ export function map(input: MapInput): Mapping | undefined {
           ...mapGoogleOptions(input.settings),
         },
       }
+    case "@ai-sdk/openai-compatible":
+      return input.providerID === "cloudflare-workers-ai"
+        ? mapCloudflareWorkers(input, baseSettings)
+        : mapOpenAICompatible(input, baseSettings)
     case "@openrouter/ai-sdk-provider":
       return mapOpenRouter(input.settings, baseSettings)
     case "@ai-sdk/xai":
@@ -65,6 +70,47 @@ export function map(input: MapInput): Mapping | undefined {
   }
 }
 
+function mapCloudflareWorkers(input: MapInput, baseSettings: Readonly<Record<string, unknown>>): Mapping {
+  const accountId = typeof input.settings.accountId === "string" ? input.settings.accountId : undefined
+  const configured = typeof baseSettings.baseURL === "string" ? baseSettings.baseURL : undefined
+  const baseURL =
+    configured && accountId
+      ? configured.replaceAll("${CLOUDFLARE_ACCOUNT_ID}", encodeURIComponent(accountId))
+      : configured
+  return {
+    package: "@opencode-ai/ai/providers/cloudflare/workers-ai",
+    settings: {
+      ...(baseURL?.includes("${CLOUDFLARE_ACCOUNT_ID}") ? {} : baseURL ? { baseURL } : {}),
+      ...mapAPIKey(input.settings),
+      ...(accountId ? { accountId } : {}),
+      ...mapOpenAICompatibleOptions(input.settings, ["accountId"]),
+    },
+  }
+}
+
+function mapOpenAICompatible(
+  input: MapInput,
+  baseSettings: Readonly<Record<string, unknown>>,
+): Mapping | undefined {
+  if (typeof baseSettings.baseURL !== "string") return
+  return {
+    package: "@opencode-ai/ai/providers/openai-compatible",
+    settings: {
+      ...baseSettings,
+      ...mapAPIKey(input.settings),
+      provider: input.providerID,
+      ...mapOpenAICompatibleOptions(input.settings),
+    },
+  }
+}
+
+function mapOpenAICompatibleOptions(settings: Readonly<Record<string, unknown>>, exclude: readonly string[] = []) {
+  const options = Object.fromEntries(
+    Object.entries(settings).filter(([key]) => !["apiKey", "baseURL", ...exclude].includes(key)),
+  )
+  return Object.keys(options).length === 0 ? {} : { providerOptions: { openai: options } }
+}
+
 function mapBedrockMantle(input: MapInput, baseSettings: Readonly<Record<string, unknown>>): Mapping | undefined {
   const settings = input.settings
   const chat = input.modelID === "openai.gpt-oss-safeguard-20b" || input.modelID === "openai.gpt-oss-safeguard-120b"

+ 1 - 13
packages/core/src/model-resolver.ts

@@ -5,8 +5,6 @@ import { LanguageModel } from "@opencode-ai/ai"
 // ast-grep-ignore: no-star-import
 import * as AnthropicMessages from "@opencode-ai/ai/protocols/anthropic-messages"
 // ast-grep-ignore: no-star-import
-import * as OpenAICompatibleChat from "@opencode-ai/ai/protocols/openai-compatible-chat"
-// ast-grep-ignore: no-star-import
 import * as OpenAIResponses from "@opencode-ai/ai/protocols/openai-responses"
 import { Auth, type AnyRoute } from "@opencode-ai/ai/route"
 import { Context, Effect, Layer, Schema } from "effect"
@@ -164,21 +162,11 @@ export const fromCatalogModel = (
         .model({ id: resolved.modelID ?? resolved.id, compatibility: resolved.compatibility }),
     )
   }
-  if (
-    Provider.isAISDK(resolved.package) &&
-    packageName === "@ai-sdk/openai-compatible" &&
-    typeof resolved.settings?.baseURL === "string"
-  ) {
-    return Effect.succeed(
-      withDefaults(resolved, OpenAICompatibleChat.route)
-        .with({ auth: key === undefined ? Auth.none : Auth.bearer(key) })
-        .model({ id: resolved.modelID ?? resolved.id, compatibility: resolved.compatibility }),
-    )
-  }
   const configured = { ...resolved.settings, ...credential?.metadata }
   const mapping = Provider.isAISDK(resolved.package)
     ? AISDKNative.map({
         packageName,
+        providerID: resolved.providerID,
         settings: configured,
         modelID: resolved.modelID ?? resolved.id,
       })

+ 12 - 55
packages/core/src/plugin/provider/cloudflare-workers-ai.ts

@@ -14,38 +14,20 @@ export const CloudflareWorkersAIPlugin = define({
       if (!item) return
       evt.provider.update(item.provider.id, (provider) => {
         if (!Provider.isAISDK(provider.package)) return
-        if (typeof provider.settings?.baseURL === "string") return
         const accountId = resolveAccountId(provider.settings ?? {})
-        if (accountId) provider.settings = { ...provider.settings, baseURL: workersEndpoint(accountId) }
+        if (accountId)
+          provider.settings = {
+            ...provider.settings,
+            baseURL:
+              typeof provider.settings?.baseURL === "string"
+                ? provider.settings.baseURL.replaceAll("${CLOUDFLARE_ACCOUNT_ID}", encodeURIComponent(accountId))
+                : workersEndpoint(accountId),
+          }
+        provider.headers = Provider.mergeHeaders(provider.headers, {
+          "User-Agent": `${App.useragent(ctx.app)} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`,
+        })
       })
     })
-    yield* ctx.aisdk.hook(
-      "sdk",
-      Effect.fn(function* (evt) {
-        if (evt.model.providerID !== providerID) return
-        if (evt.package !== "@ai-sdk/openai-compatible") return
-
-        const accountId = resolveAccountId(evt.options)
-        if (!hasWorkersEndpoint(evt.model) && !accountId) return
-        const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible"))
-        evt.sdk = mod.createOpenAICompatible(
-          sdkOptions(
-            {
-              ...evt.options,
-              baseURL: evt.options.baseURL ?? (accountId ? workersEndpoint(accountId) : undefined),
-            },
-            ctx.app,
-          ) as any,
-        )
-      }),
-    )
-    yield* ctx.aisdk.hook(
-      "language",
-      Effect.fn(function* (evt) {
-        if (evt.model.providerID !== providerID) return
-        evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id)
-      }),
-    )
   }),
 })
 
@@ -54,32 +36,7 @@ function resolveAccountId(options: Record<string, unknown>) {
 }
 
 function workersEndpoint(accountId: string) {
-  return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`
-}
-
-function hasWorkersEndpoint(model: {
-  readonly package?: string
-  readonly settings?: Readonly<Record<string, unknown>>
-}) {
-  return Provider.isAISDK(model.package) && typeof model.settings?.baseURL === "string"
-}
-
-function sdkOptions(options: Record<string, any>, app: App.Info) {
-  return {
-    ...options,
-    baseURL: expandAccountId(options.baseURL),
-    apiKey: process.env.CLOUDFLARE_API_KEY ?? options.apiKey,
-    headers: {
-      "User-Agent": `${App.useragent(app)} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`,
-      ...options.headers,
-    },
-    name: providerID,
-  }
-}
-
-function expandAccountId(baseURL: unknown) {
-  if (typeof baseURL !== "string") return baseURL
-  return baseURL.replaceAll("${CLOUDFLARE_ACCOUNT_ID}", process.env.CLOUDFLARE_ACCOUNT_ID ?? "${CLOUDFLARE_ACCOUNT_ID}")
+  return `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountId)}/ai/v1`
 }
 
 function stringOption(options: Record<string, unknown>, key: string) {

+ 4 - 0
packages/core/src/provider.ts

@@ -47,6 +47,10 @@ const builtins = new Map<string, () => Promise<unknown>>([
   ["@opencode-ai/ai/providers/azure", () => import("@opencode-ai/ai/providers/azure")],
   ["@opencode-ai/ai/providers/azure/chat", () => import("@opencode-ai/ai/providers/azure/chat")],
   ["@opencode-ai/ai/providers/azure/responses", () => import("@opencode-ai/ai/providers/azure/responses")],
+  [
+    "@opencode-ai/ai/providers/cloudflare/workers-ai",
+    () => import("@opencode-ai/ai/providers/cloudflare/workers-ai"),
+  ],
   ["@opencode-ai/ai/providers/google", () => import("@opencode-ai/ai/providers/google")],
   ["@opencode-ai/ai/providers/openai", () => import("@opencode-ai/ai/providers/openai")],
   ["@opencode-ai/ai/providers/openai/chat", () => import("@opencode-ai/ai/providers/openai/chat")],

+ 48 - 2
packages/core/test/aisdk-native.test.ts

@@ -1,8 +1,12 @@
 import { describe, expect, test } from "bun:test"
 import { AISDKNative } from "@opencode-ai/core/aisdk-native"
 
-const map = (packageName: string, settings: Readonly<Record<string, unknown>>, modelID = "test-model") =>
-  AISDKNative.map({ packageName, settings, modelID })
+const map = (
+  packageName: string,
+  settings: Readonly<Record<string, unknown>>,
+  modelID = "test-model",
+  providerID = "test-provider",
+) => AISDKNative.map({ packageName, providerID, settings, modelID })
 
 describe("AISDKNative", () => {
   test("maps both models.dev Bedrock packages to native providers", () => {
@@ -41,6 +45,48 @@ describe("AISDKNative", () => {
     )
   })
 
+  test("maps Cloudflare Workers AI to its native provider", () => {
+    expect(
+      map(
+        "@ai-sdk/openai-compatible",
+        {
+          accountId: "account/id",
+          apiKey: "secret",
+          baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
+          reasoningEffort: "high",
+        },
+        "@cf/model",
+        "cloudflare-workers-ai",
+      ),
+    ).toEqual({
+      package: "@opencode-ai/ai/providers/cloudflare/workers-ai",
+      settings: {
+        accountId: "account/id",
+        apiKey: "secret",
+        baseURL: "https://api.cloudflare.com/client/v4/accounts/account%2Fid/ai/v1",
+        providerOptions: { openai: { reasoningEffort: "high" } },
+      },
+    })
+  })
+
+  test("maps generic OpenAI-compatible providers to the native package", () => {
+    expect(
+      map("@ai-sdk/openai-compatible", {
+        apiKey: "secret",
+        baseURL: "https://provider.example/v1",
+        reasoningEffort: "high",
+      }),
+    ).toEqual({
+      package: "@opencode-ai/ai/providers/openai-compatible",
+      settings: {
+        apiKey: "secret",
+        baseURL: "https://provider.example/v1",
+        provider: "test-provider",
+        providerOptions: { openai: { reasoningEffort: "high" } },
+      },
+    })
+  })
+
   test("maps Bedrock provider and request options", () => {
     expect(
       map(

+ 28 - 0
packages/core/test/model-resolver.test.ts

@@ -131,6 +131,34 @@ describe("ModelResolver", () => {
     }),
   )
 
+  it.effect("routes Cloudflare Workers AI through its native provider", () =>
+    Effect.gen(function* () {
+      const resolved = yield* ModelResolver.fromCatalogModel(
+        model(Provider.aisdk("@ai-sdk/openai-compatible"), {
+          providerID: Provider.ID.make("cloudflare-workers-ai"),
+          modelID: "@cf/meta/llama-3.1-8b-instruct",
+          settings: {
+            baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
+          },
+        }),
+        Credential.Key.make({ type: "key", key: "secret", metadata: { accountId: "account/id" } }),
+        { loadAISDK: () => Effect.die("AI SDK loader should not be called") },
+      )
+      const headers = yield* resolved.route.auth.apply({
+        request: LLM.request({ model: resolved, prompt: "Hello" }),
+        method: "POST",
+        url: "https://example.com",
+        body: "{}",
+        headers: Headers.empty,
+      })
+
+      expect(resolved.route.id).toBe("cloudflare-workers-ai")
+      expect(resolved.route.endpoint.baseURL).toBe("https://api.cloudflare.com/client/v4/accounts/account%2Fid/ai/v1")
+      expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } })
+      expect(headers.authorization).toBe("Bearer secret")
+    }),
+  )
+
   it.effect("uses the API modelID instead of the catalog ID for native OpenAI routes", () =>
     Effect.gen(function* () {
       const catalog = model(Provider.aisdk("@ai-sdk/openai"), {

+ 39 - 210
packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts

@@ -1,13 +1,10 @@
-import { AISDK } from "@opencode-ai/core/aisdk"
-import { describe, expect } from "bun:test"
-import { Effect } from "effect"
 import { Catalog } from "@opencode-ai/core/catalog"
-import { Model } from "@opencode-ai/core/model"
 import { Plugin } from "@opencode-ai/core/plugin"
 import { PluginHost } from "@opencode-ai/core/plugin/host"
 import { CloudflareWorkersAIPlugin } from "@opencode-ai/core/plugin/provider/cloudflare-workers-ai"
 import { Provider } from "@opencode-ai/core/provider"
-import type { LanguageModelV3 } from "@ai-sdk/provider"
+import { describe, expect } from "bun:test"
+import { Effect } from "effect"
 import { testEffect } from "../lib/effect"
 import { PluginTestLayer } from "./fixture"
 
@@ -15,9 +12,7 @@ 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)
+  yield* CloudflareWorkersAIPlugin.effect(yield* PluginHost.make(plugin))
 })
 
 function required<T>(value: T | undefined): T {
@@ -25,243 +20,77 @@ function required<T>(value: T | undefined): T {
   return value
 }
 
-function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () => Effect.Effect<A, E, R>) {
+function withEnv<A, E, R>(value: string | undefined, effect: () => Effect.Effect<A, E, R>) {
   return Effect.acquireUseRelease(
     Effect.sync(() => {
-      const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]]))
-      Object.entries(vars).forEach(([key, value]) => {
-        if (value === undefined) delete process.env[key]
-        else process.env[key] = value
-      })
+      const previous = process.env.CLOUDFLARE_ACCOUNT_ID
+      if (value === undefined) delete process.env.CLOUDFLARE_ACCOUNT_ID
+      else process.env.CLOUDFLARE_ACCOUNT_ID = value
       return previous
     }),
     effect,
     (previous) =>
-      Effect.sync(() =>
-        Object.entries(previous).forEach(([key, value]) => {
-          if (value === undefined) delete process.env[key]
-          else process.env[key] = value
-        }),
-      ),
-  )
-}
-
-function fakeSelectorSdk(calls: string[]) {
-  const make = (method: string) => (id: string) => {
-    calls.push(`${method}:${id}`)
-    return { modelId: id, provider: method, specificationVersion: "v3" } as unknown as LanguageModelV3
-  }
-  return {
-    responses: make("responses"),
-    messages: make("messages"),
-    chat: make("chat"),
-    languageModel: make("languageModel"),
-  }
-}
-
-function cloudflareLanguage(sdk: unknown, modelID = "@cf/model") {
-  return (sdk as { languageModel: (id: string) => { config: CloudflareConfig; provider: string } }).languageModel(
-    modelID,
+      Effect.sync(() => {
+        if (previous === undefined) delete process.env.CLOUDFLARE_ACCOUNT_ID
+        else process.env.CLOUDFLARE_ACCOUNT_ID = previous
+      }),
   )
 }
 
-type CloudflareConfig = {
-  url: (input: { path: string; modelId: string }) => string
-  headers: () => Record<string, string> | Promise<Record<string, string>>
-}
-
-function cloudflareURL(sdk: unknown, modelID = "@cf/model") {
-  return cloudflareLanguage(sdk, modelID).config.url({ path: "/chat/completions", modelId: modelID })
-}
-
-function cloudflareHeaders(sdk: unknown, modelID = "@cf/model") {
-  return cloudflareLanguage(sdk, modelID).config.headers()
-}
+const providerID = Provider.ID.make("cloudflare-workers-ai")
 
 describe("CloudflareWorkersAIPlugin", () => {
-  it.effect("maps account ID to endpoint URL and creates an OpenAI-compatible SDK", () =>
-    withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () =>
+  it.effect("resolves the account environment variable into the native endpoint", () =>
+    withEnv("account/id", () =>
       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")
+        yield* catalog.transform((draft) =>
+          draft.provider.update(providerID, (provider) => {
+            provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
           }),
         )
         yield* addPlugin()
-        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",
-          settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1" },
-        })
-        expect(sdk.sdk).toBeDefined()
-      }),
-    ),
-  )
 
-  it.effect("preserves a configured endpoint URL instead of deriving one from account ID", () =>
-    withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct" }, () =>
-      Effect.gen(function* () {
-        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.settings = { ...provider.settings, baseURL: "https://proxy.example/v1" }
-          }),
-        )
-        yield* addPlugin()
-        expect(required(yield* catalog.provider.get(Provider.ID.make("cloudflare-workers-ai")))).toMatchObject({
-          package: "aisdk:test-provider",
-          settings: { baseURL: "https://proxy.example/v1" },
+        expect(required(yield* catalog.provider.get(providerID))).toMatchObject({
+          settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/account%2Fid/ai/v1" },
+          headers: { "User-Agent": expect.stringContaining("cloudflare-workers-ai") },
         })
       }),
     ),
   )
 
-  it.effect("allows a configured baseURL without account ID", () =>
-    withEnv({ CLOUDFLARE_ACCOUNT_ID: undefined, CLOUDFLARE_API_KEY: "key" }, () =>
-      Effect.gen(function* () {
-        const plugin = yield* Plugin.Service
-        const aisdk = yield* AISDK.Service
-        yield* addPlugin()
-        const result = 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: "aisdk:@ai-sdk/openai-compatible",
-            settings: { baseURL: "https://proxy.example/v1" },
-          }),
-          package: "@ai-sdk/openai-compatible",
-          options: { name: "cloudflare-workers-ai", baseURL: "https://proxy.example/v1" },
-        })
-        expect(cloudflareURL(result.sdk)).toBe("https://proxy.example/v1/chat/completions")
-      }),
-    ),
-  )
-
-  it.effect("uses env account ID over configured account ID", () =>
-    withEnv({ CLOUDFLARE_ACCOUNT_ID: "env-acct" }, () =>
+  it.effect("expands account placeholders and preserves configured endpoints", () =>
+    withEnv("env-account", () =>
       Effect.gen(function* () {
         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.settings = { ...provider.settings, accountId: "configured-acct" }
+        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",
+            }
           }),
         )
         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" },
-        })
-      }),
-    ),
-  )
-
-  it.effect("uses env API key over auth or configured API key and keeps the Cloudflare User-Agent", () =>
-    withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "env-key" }, () =>
-      Effect.gen(function* () {
-        const plugin = yield* Plugin.Service
-        const aisdk = yield* AISDK.Service
-        yield* addPlugin()
-        const result = 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: "aisdk:@ai-sdk/openai-compatible",
-            settings: { baseURL: "https://proxy.example/v1" },
-          }),
-          package: "@ai-sdk/openai-compatible",
-          options: {
-            name: "cloudflare-workers-ai",
-            apiKey: "auth-key",
-            baseURL: "https://proxy.example/v1",
-            headers: { custom: "header" },
-          },
-        })
-        const headers = yield* Effect.promise(() => Promise.resolve(cloudflareHeaders(result.sdk)))
-        expect(headers.authorization).toBe("Bearer env-key")
-        expect(headers.custom).toBe("header")
-        expect(headers["user-agent"]).toMatch(/^opencode\/.* cloudflare-workers-ai \(.+\) ai-sdk\/openai-compatible\//)
+        expect(required(yield* catalog.provider.get(providerID)).settings?.baseURL).toBe(
+          "https://api.cloudflare.com/client/v4/accounts/env-account/ai/v1",
+        )
       }),
     ),
   )
 
-  it.effect("expands account ID vars in endpoint URLs", () =>
-    withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () =>
+  it.effect("preserves a custom endpoint without an account ID", () =>
+    withEnv(undefined, () =>
       Effect.gen(function* () {
-        const plugin = yield* Plugin.Service
-        const aisdk = yield* AISDK.Service
-        yield* addPlugin()
-        const result = 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: "aisdk:@ai-sdk/openai-compatible",
-            settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1" },
+        const catalog = yield* Catalog.Service
+        yield* catalog.transform((draft) =>
+          draft.provider.update(providerID, (provider) => {
+            provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
+            provider.settings = { baseURL: "https://proxy.example/v1" }
           }),
-          package: "@ai-sdk/openai-compatible",
-          options: {
-            name: "cloudflare-workers-ai",
-            baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
-          },
-        })
-        expect(cloudflareURL(result.sdk)).toBe(
-          "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1/chat/completions",
         )
-      }),
-    ),
-  )
-
-  it.effect("selects languageModel with the API model ID", () =>
-    Effect.gen(function* () {
-      const plugin = yield* Plugin.Service
-      const aisdk = yield* AISDK.Service
-      const calls: string[] = []
-      yield* addPlugin()
-      const result = yield* aisdk.runLanguage({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("alias")),
-          modelID: Model.ID.make("@cf/api-model"),
-          package: "aisdk:test-provider",
-        }),
-        sdk: fakeSelectorSdk(calls),
-        options: {},
-      })
-      expect(result.language).toBeDefined()
-      expect(calls).toEqual(["languageModel:@cf/api-model"])
-    }),
-  )
-
-  it.effect("does not create an SDK for non OpenAI-compatible packages", () =>
-    withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () =>
-      Effect.gen(function* () {
-        const plugin = yield* Plugin.Service
-        const aisdk = yield* AISDK.Service
         yield* addPlugin()
-        const result = 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: "aisdk:@ai-sdk/anthropic",
-            settings: { baseURL: "https://proxy.example/v1" },
-          }),
-          package: "@ai-sdk/anthropic",
-          options: { name: "cloudflare-workers-ai" },
-        })
-        expect(result.sdk).toBeUndefined()
+        expect(required(yield* catalog.provider.get(providerID)).settings?.baseURL).toBe("https://proxy.example/v1")
       }),
     ),
   )