import os from "os" import { InstallationVersion } from "../../installation/version" import { Effect, Option, Schema } from "effect" import { define } from "../internal" export const CloudflareAIGatewayPlugin = define({ id: "cloudflare-ai-gateway", effect: Effect.fn(function* (ctx) { yield* ctx.aisdk.sdk( Effect.fn(function* (evt) { if (evt.package !== "ai-gateway-provider") return if (evt.options.baseURL) return const config = gatewayConfig(evt.options) if (!config) return const metadata = gatewayMetadata(evt.options) const { createAiGateway } = yield* Effect.promise(() => import("ai-gateway-provider")).pipe(Effect.orDie) const { createUnified } = yield* Effect.promise(() => import("ai-gateway-provider/providers/unified")).pipe( Effect.orDie, ) const gateway = createAiGateway({ accountId: config.accountId, gateway: config.gatewayId, apiKey: config.apiKey, options: gatewayOptions(evt.options, metadata), } as any) evt.sdk = { languageModel(modelID: string) { // Workers AI is the only first-party provider whose upstream is Cloudflare itself, so it is // the only one that should receive the Cloudflare token as its upstream Authorization header. // The Unified API addresses Workers AI both with the explicit "workers-ai/" prefix and as // bare "@cf/..." ids. Third-party providers must not receive the token; they rely on the // gateway's stored/BYOK keys instead. const isWorkersAi = modelID.startsWith("workers-ai/") || modelID.startsWith("@cf/") const unified = createUnified(isWorkersAi ? { apiKey: config.apiKey } : {}) return gateway(unified(modelID)) }, } }), ) }), }) type GatewayConfig = { accountId: string gatewayId: string apiKey: string } const decodeJson = Schema.decodeUnknownOption(Schema.UnknownFromJsonString) function gatewayConfig(options: Record): GatewayConfig | undefined { const accountId = process.env.CLOUDFLARE_ACCOUNT_ID ?? stringOption(options, "accountId") // Credential projection copies key metadata into options. The prompt stores the // gateway as gatewayId, while older config examples may use gateway. const gatewayId = process.env.CLOUDFLARE_GATEWAY_ID ?? stringOption(options, "gatewayId") ?? stringOption(options, "gateway") const apiKey = process.env.CLOUDFLARE_API_TOKEN ?? process.env.CF_AIG_TOKEN ?? stringOption(options, "apiKey") if (!accountId || !gatewayId || !apiKey) return undefined return { accountId, gatewayId, apiKey } } function gatewayMetadata(options: Record) { // Preserve the legacy cf-aig-metadata header escape hatch for gateway logging // metadata, but prefer the typed metadata option when present. if (options.metadata !== undefined) return options.metadata const raw = (options.headers as Record | undefined)?.["cf-aig-metadata"] return raw ? Option.getOrUndefined(decodeJson(raw)) : undefined } function gatewayOptions(options: Record, metadata: unknown) { return { metadata, cacheTtl: options.cacheTtl, cacheKey: options.cacheKey, skipCache: options.skipCache, collectLog: options.collectLog, headers: { "User-Agent": `opencode/${InstallationVersion} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`, }, } } function stringOption(options: Record, key: string) { return typeof options[key] === "string" ? options[key] : undefined }