opencode / packages /core /src /plugin /provider /cloudflare-ai-gateway.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
3e05655 verified
Raw History Blame Contribute Delete
3.69 kB
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<string, unknown>): 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<string, unknown>) {
// 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<string, string> | undefined)?.["cf-aig-metadata"]
return raw ? Option.getOrUndefined(decodeJson(raw)) : undefined
}
function gatewayOptions(options: Record<string, unknown>, 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<string, unknown>, key: string) {
return typeof options[key] === "string" ? options[key] : undefined
}