diff --git a/src/components/ai-gateway/code-example-selector.tsx b/src/components/ai-gateway/code-example-selector.tsx index 9ab58fd68f7..c46dc8b31f8 100644 --- a/src/components/ai-gateway/code-example-selector.tsx +++ b/src/components/ai-gateway/code-example-selector.tsx @@ -382,58 +382,54 @@ export function CodeExample({ export const modelOptions = [ { - model: "gpt-5.2", + model: "gpt-6-luna", provider: "openai", aiSDK: { + id: "openai/gpt-6-luna", + pkg: "@ai-sdk/openai", providerFactory: "createOpenAI", - providerUsage: "openai.chat", provider: "openai", }, }, { - model: "claude-4-5-sonnet", + model: "claude-opus-5-5", provider: "anthropic", aiSDK: { + id: "anthropic/claude-opus-5.5", + pkg: "@ai-sdk/anthropic", providerFactory: "createAnthropic", - providerUsage: "anthropic", provider: "anthropic", }, }, { - model: "gemini-2.5-pro", + model: "gemini-4-argon", provider: "google", aiSDK: { - providerFactory: "createGoogle", - providerUsage: "google", + id: "google/gemini-4-argon", + pkg: "@ai-sdk/google", + providerFactory: "createGoogleGenerativeAI", provider: "google", }, }, { - model: "grok-4", + model: "grok-4.7", provider: "grok", aiSDK: { + id: "xai/grok-4.7", + pkg: "@ai-sdk/xai", providerFactory: "createXai", - providerUsage: "xai", provider: "xai", }, }, { model: "customer-support", provider: "dynamic", - aiSDK: { - providerFactory: "createUnified", - providerUsage: "unified", - provider: "unified", - }, + aiSDK: { id: "dynamic/customer-support" }, }, { model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", provider: "workers-ai", - aiSDK: { - providerFactory: "createUnified", - providerUsage: "unified", - provider: "unified", - }, + aiSDK: { id: "@cf/meta/llama-3.3-70b-instruct-fp8-fast" }, }, ]; export const code = `import OpenAI from "openai"; @@ -450,38 +446,35 @@ const response = await client.chat.completions.create({ messages: [{ role: "user", content: "Hello, world!" }], });`; -export const aiSDKUnifiedCode = `import { createAiGateway } from 'ai-gateway-provider'; -import { createUnified } from 'ai-gateway-provider/providers/unified'; +export const aiSDKUnifiedCode = `import { createAI } from "agents/models/ai-sdk"; import { generateText } from "ai"; -const aigateway = createAiGateway({ +const ai = createAI({ accountId: "{CLOUDFLARE_ACCOUNT_ID}", - gateway: '{GATEWAY_NAME}', - apiKey: '{CF_AIG_TOKEN}', + apiToken: "{CF_API_TOKEN}", + gateway: "{GATEWAY_NAME}", }); -const unified = createUnified({apikey}); - const { text } = await generateText({ - model: aigateway(unified('{provider}/{model}')), - prompt: 'What is Cloudflare?', + model: ai("{id}"), + prompt: "What is Cloudflare?", });`; -export const aiSDKNativeCode = `import { createAiGateway } from 'ai-gateway-provider'; -import { {providerFactory} } from 'ai-gateway-provider/providers/{provider}'; +export const aiSDKNativeCode = `import { createAI } from "agents/models/ai-sdk"; +import { {providerFactory} } from "{pkg}"; import { generateText } from "ai"; -const aigateway = createAiGateway({ +const ai = createAI({ accountId: "{CLOUDFLARE_ACCOUNT_ID}", - gateway: '{GATEWAY_NAME}', - apiKey: '{CF_AIG_TOKEN}', + apiToken: "{CF_API_TOKEN}", + gateway: "{GATEWAY_NAME}", }); const {provider} = {providerFactory}({apikey}); const { text } = await generateText({ - model: aigateway({providerUsage}('{model}')), - prompt: 'What is Cloudflare?', + model: ai({provider}("{model}"){vendorKey}), + prompt: "What is Cloudflare?", });`; export const curlCode = `curl -X POST https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/compat/chat/completions \\ diff --git a/src/components/ai-gateway/code-examples.astro b/src/components/ai-gateway/code-examples.astro index a67d754d6eb..52f366fe8b6 100644 --- a/src/components/ai-gateway/code-examples.astro +++ b/src/components/ai-gateway/code-examples.astro @@ -103,81 +103,74 @@ const { forceClient, forceAPI } = props.parse(Astro.props); ))} {(!forceClient || forceClient === "aisdk") && - modelOptions.map((option) => ( - - - - ))} - - {(!forceClient || forceClient === "aisdk") && - modelOptions.map((option) => ( - - - - ))} + modelOptions + .filter((option) => option.aiSDK.pkg !== undefined) + .flatMap((option) => + (["unified", "native"] as const).map((apiType) => ({ + option, + apiType, + })), + ) + .map(({ option, apiType }) => ( + + + + ))} {(!forceClient || forceClient === "aisdk") && - modelOptions.map((option) => ( - - - - ))} + modelOptions + .filter((option) => option.aiSDK.pkg !== undefined) + .map((option) => ( + + + + ))} {(!forceClient || forceClient === "curl") && modelOptions.map((option) => ( diff --git a/src/content/changelog/agents/2026-10-21-createai.mdx b/src/content/changelog/agents/2026-10-21-createai.mdx new file mode 100644 index 00000000000..2f968c40a8c --- /dev/null +++ b/src/content/changelog/agents/2026-10-21-createai.mdx @@ -0,0 +1,61 @@ +--- +title: createAI replaces workers-ai-provider and ai-gateway-provider +description: The Agents SDK has a new createAI function for the AI SDK and Pi AI that unifies workers-ai-provider and ai-gateway-provider. Both packages are now feature frozen and marked as deprecated. +products: + - agents + - workers-ai + - ai-gateway +date: 2026-10-21 +--- + +import { TypeScriptExample } from "~/components"; + +The Agents SDK has a new [`createAI`](/agents/models/) function for the [AI SDK](/agents/models/ai-sdk/) and [Pi AI](/agents/models/pi-ai/). It unifies what `workers-ai-provider` and `ai-gateway-provider` did: Workers AI models, AI Gateway catalog models, dynamic routes, and vendor models, through the `AI` binding or an account id and API token. + +`workers-ai-provider` and `ai-gateway-provider` are now feature frozen and marked as deprecated. Please migrate at your convenience. + +```sh +npm i agents +``` + + + +```ts +import { createAI } from "agents/models/ai-sdk"; +import { streamText } from "ai"; + +export default { + async fetch(request: Request, env: Env) { + const ai = createAI({ binding: env.AI, id: "my-gateway" }); + + const result = streamText({ + model: ai("anthropic/claude-opus-5.5", { + fallback: ["@cf/zai-org/glm-4.7-flash"], + }), + prompt: "Write a haiku about Durable Objects", + }); + + return result.toTextStreamResponse(); + }, +}; +``` + + + +With `createAI` you get: + +- **Every model by id.** Workers AI `@cf/` ids, any model in the [AI Gateway catalog](/ai/models/), and [dynamic routes](/ai-gateway/features/dynamic-routing/) as `dynamic/`. +- **Vendor model objects.** Pass a model from the vendor's own package, such as `ai(anthropic("claude-opus-5-5"))`. It goes unchanged to AI Gateway's [provider-native endpoint](/ai-gateway/usage/providers/). +- **The binding, or an account id and API token.** Use a gateway in another account, or call from outside a Worker. +- **Gateway options, fallback, and BYOK.** Caching, logging, metadata, retries, `byokAlias`, `zdr`, and `vendorKey`. +- **Stream resume.** Through the binding, a catalog or dynamic route stream that drops mid-answer resumes. +- **Other model types.** Embeddings, images, speech, transcription, and reranking in the AI SDK provider, and images in Pi AI. + +| Before | After | +| ------------------------------------------- | ----------------------------------------------------- | +| `createWorkersAI({ binding })` | `createAI({ binding })` | +| `createWorkersAI({ accountId, apiKey })` | `createAI({ accountId, apiToken })` | +| `createAiGateway(...)` | `createAI(...)`, with `fallback` for a list of models | +| `createAISearch` from `workers-ai-provider` | `createAISearchNamespace` from `ai-search-provider` | + +For AI Search, use [`ai-search-provider`](/ai-search/agent-sdks/ai-sdk/). For more information, refer to [Models](/agents/models/). diff --git a/src/content/docs/agent-memory/get-started.mdx b/src/content/docs/agent-memory/get-started.mdx index 52931736416..ac18713e71f 100644 --- a/src/content/docs/agent-memory/get-started.mdx +++ b/src/content/docs/agent-memory/get-started.mdx @@ -55,7 +55,7 @@ cd memory-agent Install the dependencies used by this guide: - + ## 2. Create a namespace @@ -183,7 +183,7 @@ Change the `agents` import and add the AI SDK imports. Keep the `Session` import import { Agent, getAgentByName, routeAgentRequest } from "agents"; import { convertToModelMessages, generateText, stepCountIs } from "ai"; import type { UIMessage } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; ``` @@ -218,9 +218,9 @@ export class ChatAgent extends Agent { await this.scheduleIngest(); - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = await generateText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), system: await this.session.freezeSystemPrompt(), messages: await convertToModelMessages( (await this.session.getHistory()) as UIMessage[], diff --git a/src/content/docs/agents/communication-channels/chat/chat-agents.mdx b/src/content/docs/agents/communication-channels/chat/chat-agents.mdx index 0795d15836e..d09c77754bd 100644 --- a/src/content/docs/agents/communication-channels/chat/chat-agents.mdx +++ b/src/content/docs/agents/communication-channels/chat/chat-agents.mdx @@ -40,7 +40,7 @@ Built on the [AI SDK](https://ai-sdk.dev) and Cloudflare Durable Objects, you ge ### Install - + ### Server @@ -48,16 +48,16 @@ Built on the [AI SDK](https://ai-sdk.dev) and Cloudflare Durable Objects, you ge ```ts import { AIChatAgent } from "@cloudflare/ai-chat"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { streamText, convertToModelMessages } from "ai"; export class ChatAgent extends AIChatAgent { async onChatMessage() { - // Use any provider such as workers-ai-provider, openai, anthropic, google, etc. - const workersai = createWorkersAI({ binding: this.env.AI }); + // Use any AI SDK provider, such as createAI from agents/models/ai-sdk, openai, anthropic, or google. + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), messages: await convertToModelMessages(this.messages), }); @@ -194,10 +194,10 @@ This is the main method you override. It receives the conversation context and s ```ts export class ChatAgent extends AIChatAgent { async onChatMessage() { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), system: "You are a helpful assistant.", messages: await convertToModelMessages(this.messages), }); @@ -270,10 +270,10 @@ import { streamText, convertToModelMessages, pruneMessages } from "ai"; export class ChatAgent extends AIChatAgent { async onChatMessage() { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), messages: pruneMessages({ messages: await convertToModelMessages(this.messages), reasoning: "before-last-message", @@ -550,7 +550,7 @@ When a user clicks "stop" in the chat UI, the client sends a `CF_AGENT_CHAT_REQU export class ChatAgent extends AIChatAgent { async onChatMessage(_onFinish, options) { const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), messages: await convertToModelMessages(this.messages), abortSignal: options?.abortSignal, // Pass through for cancellation }); @@ -949,10 +949,10 @@ import { streamText, convertToModelMessages, tool, stepCountIs } from "ai"; import { z } from "zod"; export class ChatAgent extends AIChatAgent { async onChatMessage() { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), messages: await convertToModelMessages(this.messages), tools: { getWeather: tool({ @@ -1201,12 +1201,12 @@ import { export class ChatAgent extends AIChatAgent { async onChatMessage() { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const stream = createUIMessageStream({ execute: async ({ writer }) => { const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), messages: await convertToModelMessages(this.messages), }); @@ -1380,7 +1380,7 @@ Storage (`maxPersistedMessages`) and LLM context are independent: export class ChatAgent extends AIChatAgent { async onChatMessage() { const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), messages: pruneMessages({ // LLM context limit messages: await convertToModelMessages(this.messages), @@ -1405,11 +1405,11 @@ export class ChatAgent extends AIChatAgent { ```ts -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; -const workersai = createWorkersAI({ binding: this.env.AI }); +const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), messages: await convertToModelMessages(this.messages), }); ``` diff --git a/src/content/docs/agents/communication-channels/voice.mdx b/src/content/docs/agents/communication-channels/voice.mdx index 2314189c260..1d54af060c3 100644 --- a/src/content/docs/agents/communication-channels/voice.mdx +++ b/src/content/docs/agents/communication-channels/voice.mdx @@ -243,17 +243,17 @@ export class MyAgent extends VoiceAgent { ```ts import { streamText } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; export class MyAgent extends VoiceAgent { transcriber = new WorkersAIFluxSTT(this.env.AI); tts = new WorkersAITTS(this.env.AI); async onTurn(transcript: string, context: VoiceTurnContext) { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/moonshotai/kimi-k2.6"), + model: ai("@cf/moonshotai/kimi-k2.6"), system: "You are a helpful voice assistant. Keep responses concise.", messages: [ ...context.messages.map((m) => ({ diff --git a/src/content/docs/agents/concepts/calling-llms.mdx b/src/content/docs/agents/concepts/calling-llms.mdx index 8ad30643d7c..45d48faf6ac 100644 --- a/src/content/docs/agents/concepts/calling-llms.mdx +++ b/src/content/docs/agents/concepts/calling-llms.mdx @@ -58,15 +58,15 @@ An Agent keeps running after the client disconnects. When the response arrives, ```ts import { Agent } from "agents"; import { streamText } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; export class MyAgent extends Agent { async onMessage(connection: Connection, message: WSMessage) { const { prompt } = JSON.parse(message as string); - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), prompt, }); @@ -103,9 +103,9 @@ export class DigestAgent extends Agent { SELECT title, body FROM articles WHERE created_at > datetime('now', '-1 day')`; - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const { text } = await generateText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), prompt: `Summarize these articles:\n${articles.map((a) => a.title + ": " + a.body).join("\n\n")}`, }); @@ -128,25 +128,25 @@ Because an Agent maintains state across calls, you can chain multiple models in ```ts import { Agent } from "agents"; import { generateText, embed } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; export class TriageAgent extends Agent { async triage(ticket: string) { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const { text: category } = await generateText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), prompt: `Classify this support ticket into one of: billing, technical, account. Ticket: ${ticket}`, }); const { embedding } = await embed({ - model: workersai("@cf/baai/bge-base-en-v1.5"), + model: ai.embedding("@cf/baai/bge-base-en-v1.5"), value: ticket, }); const similar = await this.env.VECTOR_DB.query(embedding, { topK: 5 }); const { text: response } = await generateText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), prompt: `Draft a response for this ${category} ticket. Similar resolved tickets: ${JSON.stringify(similar)}. Ticket: ${ticket}`, }); @@ -180,9 +180,9 @@ export class CachingAgent extends Agent { return cached[0].response; } - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const { text } = await generateText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), prompt, }); diff --git a/src/content/docs/agents/examples/browser-agent.mdx b/src/content/docs/agents/examples/browser-agent.mdx index 2a0985df7ba..bfd90b3d310 100644 --- a/src/content/docs/agents/examples/browser-agent.mdx +++ b/src/content/docs/agents/examples/browser-agent.mdx @@ -93,12 +93,12 @@ Pass browser tools alongside your other tools. The `model` can be any AI SDK pro ```ts import { streamText } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; -const workersai = createWorkersAI({ binding: env.AI }); +const ai = createAI({ binding: env.AI }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), system: "You are a helpful assistant that can inspect web pages.", messages, tools: { @@ -151,19 +151,19 @@ The typical pattern is to create browser tools inside an [`AIChatAgent`](/agents ```ts import { AIChatAgent } from "@cloudflare/ai-chat"; import { createBrowserTools } from "agents/browser/ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { streamText, convertToModelMessages, stepCountIs } from "ai"; export class MyAgent extends AIChatAgent { async onChatMessage() { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const browserTools = createBrowserTools({ browser: this.env.BROWSER, loader: this.env.LOADER, }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), system: "You can browse the web and inspect pages.", messages: await convertToModelMessages(this.messages), tools: { diff --git a/src/content/docs/agents/examples/chat-agent.mdx b/src/content/docs/agents/examples/chat-agent.mdx index 1f44045af8d..32169801577 100644 --- a/src/content/docs/agents/examples/chat-agent.mdx +++ b/src/content/docs/agents/examples/chat-agent.mdx @@ -35,7 +35,7 @@ Select **"Hello World" Worker** when prompted. Then install the dependencies: ```sh cd chat-agent -npm install agents @cloudflare/ai-chat ai workers-ai-provider zod +npm install agents @cloudflare/ai-chat ai @ai-sdk/provider zod ``` ## 2. Configure Wrangler @@ -76,7 +76,7 @@ Create `src/server.ts`. This is where your agent lives: ```ts import { AIChatAgent } from "@cloudflare/ai-chat"; import { routeAgentRequest } from "agents"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { streamText, convertToModelMessages, @@ -88,10 +88,10 @@ import { z } from "zod"; export class ChatAgent extends AIChatAgent { async onChatMessage() { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/meta/llama-4-scout-17b-16e-instruct"), + model: ai("@cf/meta/llama-4-scout-17b-16e-instruct"), system: "You are a helpful assistant. You can check the weather, " + "get the user's timezone, and run calculations.", diff --git a/src/content/docs/agents/examples/voice-agent.mdx b/src/content/docs/agents/examples/voice-agent.mdx index a20ef51b612..1a439fa4cd6 100644 --- a/src/content/docs/agents/examples/voice-agent.mdx +++ b/src/content/docs/agents/examples/voice-agent.mdx @@ -94,7 +94,7 @@ import { type VoiceTurnContext, } from "@cloudflare/voice"; import { streamText, tool, stepCountIs } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { z } from "zod"; const VoiceAgent = withVoice(Agent); @@ -104,10 +104,10 @@ export class MyVoiceAgent extends VoiceAgent { tts = new WorkersAITTS(this.env.AI); async onTurn(transcript: string, context: VoiceTurnContext) { - const workersAi = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersAi("@cf/moonshotai/kimi-k2.6"), + model: ai("@cf/moonshotai/kimi-k2.6"), system: "You are a helpful voice assistant. Keep responses concise — you are being spoken aloud.", messages: [ diff --git a/src/content/docs/agents/harnesses/opencode.mdx b/src/content/docs/agents/harnesses/opencode.mdx index 06bd00bd040..35824d3ea1c 100644 --- a/src/content/docs/agents/harnesses/opencode.mdx +++ b/src/content/docs/agents/harnesses/opencode.mdx @@ -1,6 +1,6 @@ --- title: OpenCode -description: Run OpenCode 2.0 in a plain Durable Object with the Agents SDK, Workers AI models, bundled plugins, and durable sessions. +description: Run OpenCode 2.0 in a plain Durable Object with the Agents SDK, Workers AI and AI Gateway models, bundled plugins, and durable sessions. pcx_content_type: overview sidebar: order: 11 @@ -18,7 +18,7 @@ import { [OpenCode](https://opencode.ai/v2/docs/build/sdk/) is an extensible agent harness. The Agents SDK's `OpenCodeHarness` runs its Workerd SDK inside a plain Durable Object. Refer to [OpenCode's Cloudflare SDK documentation](https://opencode.ai/v2/docs/build/sdk/cloudflare/) for the embedded host API. -OpenCode owns the agent loop, transcript, tools, and session recovery. `OpenCodeHarness` supplies SQLite storage and wakes the object after an interrupted run. The `agents/models/opencode` provider connects OpenCode to Workers AI through your `AI` binding. +OpenCode owns the agent loop, transcript, tools, and session recovery. `OpenCodeHarness` supplies SQLite storage and wakes the object after an interrupted run. The `agents/models/opencode` provider connects OpenCode to Workers AI models, [AI Gateway](/ai-gateway/) catalog models, and dynamic routes through your `AI` binding. sqlite[("SQLite: opencode_ tables")] host <--> plugins["Bundled plugins"] end - host --> ai["Workers AI binding"] + host --> ai["AI binding"] plugins --> mcp["Remote MCP server"] plugins --> workspace["External execution environment"] ``` @@ -52,7 +52,7 @@ flowchart LR | Capability | Support | | ---------------------------------------------- | -------------------------------------------------------- | | Durable sessions and interrupted-turn recovery | OpenCode persists state; Lifecycle jobs wake the object. | -| Workers AI models | Register the `agents/models/opencode` plugin. | +| Workers AI and AI Gateway models | Register the `agents/models/opencode` plugin. | | Custom tools, agents, and skills | Bundle OpenCode plugins with your Worker. | | Remote Model Context Protocol (MCP) servers | Register remote servers through a plugin. | | Live events and durable event replay | Use `session.events()` and `session.log()`. | @@ -63,9 +63,9 @@ Install the Agents SDK and OpenCode packages: -Use `agents` version 0.28.1 or later. The OpenCode packages are optional peer dependencies, required by this integration, with a supported range of `^2.0.24`. +Use the latest `agents` release. The OpenCode packages are optional peer dependencies, required by this integration, with a supported range of `^2.0.24`. -Add the Workers AI binding, a SQLite-backed Durable Object, and `nodejs_compat` to your Wrangler configuration: +Add the `AI` binding, a SQLite-backed Durable Object, and `nodejs_compat` to your Wrangler configuration: @@ -172,12 +172,26 @@ export default { -Pass the factory's `storage` to `OpenCodeWorkerd.create()` so OpenCode uses the prefixed database. Register `this.ai.plugin` to make the Workers AI models available. `models: { fetch: false }` skips OpenCode's external model catalog refresh; the provider registers models you name with `this.ai()`. +Pass the factory's `storage` to `OpenCodeWorkerd.create()` so OpenCode uses the prefixed database. Register `this.ai.plugin` to add a provider named "Cloudflare" (id `cloudflare`) to OpenCode. `models: { fetch: false }` skips OpenCode's external model catalog refresh. The provider registers the models you name with `this.ai()`. The Lifecycle manages host startup. The factory runs again when the harness reopens an idle host, so it must support repeated calls. The `"demo"` object name is for this example. In an application, choose an object name based on an authenticated user or conversation. +## Choose a model + +`this.ai()` takes the same model ids as the [AI SDK provider](/agents/models/ai-sdk/), and accepts its model options: + +```ts +this.ai("@cf/moonshotai/kimi-k2.7-code"); // Workers AI +this.ai("anthropic/claude-opus-5.5"); // AI Gateway catalog +this.ai("dynamic/support"); // AI Gateway dynamic route +``` + +A catalog id uses the [model catalog](/ai/models/)'s spelling, such as `anthropic/claude-opus-5.5`. A dynamic route belongs to one gateway, so pass that gateway's `id` to `createAI`. An id that is not one of these three forms throws a `TypeError` on the line that names it. Streams from catalog models and dynamic routes [resume after a dropped connection](/agents/models/ai-sdk/#resume-a-dropped-stream). + +Each call returns OpenCode's model reference, `{ providerID, id }`. Pass it to `defaults.model` or `session.setModel()`. To set how OpenCode budgets a model's context, pass `limit: { context, output }`. The default is 128,000 context tokens and 8,192 output tokens. + ## Send prompts `harness.prompt()` submits to the root session and waits for completion. It returns `status`, optional `text` or `reason`, and the active transcript as `messages`. Check `status`: `"done"` means answered; `"unanswered"` means failed, interrupted, withdrawn, or not found. diff --git a/src/content/docs/agents/harnesses/pi/index.mdx b/src/content/docs/agents/harnesses/pi/index.mdx index 2d99e4f7ad1..13d6f393890 100644 --- a/src/content/docs/agents/harnesses/pi/index.mdx +++ b/src/content/docs/agents/harnesses/pi/index.mdx @@ -51,7 +51,7 @@ Install `agents` with Pi Durable and Pi AI: -Both Pi packages are optional peer dependencies of `agents`. `PiHarness` needs version 1.0 or later of each. +Both Pi packages are optional peer dependencies of `agents`. `PiHarness` needs version 1.3 or later of each. The `agents/models/pi-ai` entry point supports AI Gateway and Workers AI models, so you can get started with Cloudflare models right away or use your existing Pi AI provider. The examples on this page use Workers AI through the `AI` binding and the [Pi AI model provider](/agents/models/pi-ai/). Add the binding and a SQLite-backed Durable Object to your Wrangler configuration: diff --git a/src/content/docs/agents/harnesses/think/configuration.mdx b/src/content/docs/agents/harnesses/think/configuration.mdx index ed1f3c95439..f6e15ba066c 100644 --- a/src/content/docs/agents/harnesses/think/configuration.mdx +++ b/src/content/docs/agents/harnesses/think/configuration.mdx @@ -52,7 +52,7 @@ export class MyAgent extends Think { fast: "@cf/moonshotai/kimi-k2.6", capable: "@cf/meta/llama-4-scout-17b-16e-instruct", }; - return createWorkersAI({ binding: this.env.AI })(models[tier]); + return createAI({ binding: this.env.AI })(models[tier]); } } ``` diff --git a/src/content/docs/agents/harnesses/think/getting-started.mdx b/src/content/docs/agents/harnesses/think/getting-started.mdx index 428046a1c87..065acce96db 100644 --- a/src/content/docs/agents/harnesses/think/getting-started.mdx +++ b/src/content/docs/agents/harnesses/think/getting-started.mdx @@ -37,7 +37,7 @@ npm init -y Install dependencies: ```sh -npm install @cloudflare/think @cloudflare/computer @cloudflare/ai-chat agents ai zod workers-ai-provider react react-dom +npm install @cloudflare/think @cloudflare/computer @cloudflare/ai-chat agents ai zod @ai-sdk/provider react react-dom npm install -D wrangler @cloudflare/vite-plugin @cloudflare/workers-types @vitejs/plugin-react @tailwindcss/vite tailwindcss typescript vite ``` @@ -101,12 +101,12 @@ Create `src/server.ts`: ```ts import { Think } from "@cloudflare/think"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { routeAgentRequest } from "agents"; export class MyAgent extends Think { getModel() { - return createWorkersAI({ binding: this.env.AI })( + return createAI({ binding: this.env.AI })( "@cf/moonshotai/kimi-k2.6", ); } @@ -226,7 +226,7 @@ Override `configureSession` to give the model writable memory that survives rest ```ts export class MyAgent extends Think { getModel(): LanguageModel { - return createWorkersAI({ binding: this.env.AI })( + return createAI({ binding: this.env.AI })( "@cf/moonshotai/kimi-k2.6", ); } diff --git a/src/content/docs/agents/harnesses/think/index.mdx b/src/content/docs/agents/harnesses/think/index.mdx index e11a8dbfe35..4fa40869ad9 100644 --- a/src/content/docs/agents/harnesses/think/index.mdx +++ b/src/content/docs/agents/harnesses/think/index.mdx @@ -32,7 +32,7 @@ If this is your first agent, start with the [Getting started tutorial](/agents/h ### Install - + Think supports AI SDK v6 and v7. Use `ai@^6` with `@ai-sdk/react@^3`, or use `ai@^7` with `@ai-sdk/react@^4`. Keep the AI SDK packages on matching major versions throughout your project. @@ -42,12 +42,12 @@ Think supports AI SDK v6 and v7. Use `ai@^6` with `@ai-sdk/react@^3`, or use `ai ```ts import { Think } from "@cloudflare/think"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { routeAgentRequest } from "agents"; export class MyAgent extends Think { getModel() { - return createWorkersAI({ binding: this.env.AI })( + return createAI({ binding: this.env.AI })( "@cf/moonshotai/kimi-k2.6", ); } diff --git a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx index 2b061bc435e..097dccef32a 100644 --- a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx @@ -349,13 +349,13 @@ To use MCP tools with the AI SDK, use `this.mcp.getAITools()` which converts MCP ```ts import { generateText } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; export class MyAgent extends Agent { async onRequest(request: Request) { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const response = await generateText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), prompt: "What's the weather in San Francisco?", tools: this.mcp.getAITools(), }); diff --git a/src/content/docs/agents/models/ai-sdk.mdx b/src/content/docs/agents/models/ai-sdk.mdx index 5f628c0ab15..3b2b54ae5c2 100644 --- a/src/content/docs/agents/models/ai-sdk.mdx +++ b/src/content/docs/agents/models/ai-sdk.mdx @@ -1,7 +1,7 @@ --- title: AI SDK pcx_content_type: how-to -description: Use createAI from agents/models/ai-sdk to call Workers AI models and route third-party AI SDK models through AI Gateway. +description: Use createAI from agents/models/ai-sdk to call Workers AI models, AI Gateway catalog models, dynamic routes, and vendor AI SDK models through the AI binding or an account id and API token. tags: - AI sidebar: @@ -19,6 +19,22 @@ import { `agents/models/ai-sdk` is an [AI SDK](https://ai-sdk.dev/) provider for Workers AI and AI Gateway. `createAI` returns a full `ProviderV4`, so its models work anywhere the AI SDK takes a model: `generateText`, `streamText`, tools, structured output, and [Think](/agents/harnesses/think/). +The provider takes two kinds of model. Both go through the `AI` binding, or over HTTP with an [account id and API token](#use-an-account-id-and-api-token): + +```ts +// A string runs on env.AI.run. +ai("@cf/zai-org/glm-4.7-flash"); // Workers AI +ai("anthropic/claude-opus-5.5"); // AI Gateway catalog +ai("dynamic/support"); // AI Gateway dynamic route + +// A vendor's own model object goes to AI Gateway's provider-native endpoint. +ai(anthropic("claude-opus-5-5")); +``` + +A string needs no vendor package and no vendor key. A model object keeps the vendor's own request builder and parser. + +`createAI` replaces `workers-ai-provider` and `ai-gateway-provider`. For AI Search, use `ai-search-provider`. + :::caution[Beta] This provider is in beta. Its API may change in a minor release. ::: @@ -29,7 +45,7 @@ The provider needs `ai@^7` and `@ai-sdk/provider@^4`. Both are optional peer dep -Add a vendor's AI SDK provider only if you call that vendor's models, for example `@ai-sdk/anthropic` or `@ai-sdk/openai`. +Add a vendor's AI SDK provider only if you pass that vendor's model objects, for example `@ai-sdk/anthropic` or `@ai-sdk/openai`. Catalog ids do not need one. Add the `AI` binding to your Wrangler configuration: @@ -45,6 +61,22 @@ Add the `AI` binding to your Wrangler configuration: +## Use an account id and API token + +To use a gateway in another account, or to call from outside a Worker, pass `accountId` and `apiToken` instead of `binding`: + +```ts +const ai = createAI({ + accountId: "", + apiToken: env.CLOUDFLARE_API_TOKEN, + gateway: "my-gateway", +}); +``` + +The call forms and options are the same, and the gateway still defaults to `default`. A string goes to AI Gateway's [REST API](/ai-gateway/usage/rest-api/), which runs it as `env.AI.run()` does. A model object goes to the [provider-native endpoint](/ai-gateway/usage/providers/) at `gateway.ai.cloudflare.com`, with the token in the `cf-aig-authorization` header. The token is sent to those two hosts only. + +[Create an API token](/fundamentals/api/get-started/create-token/) with the **Workers AI Read** permission for strings and **AI Gateway Run** for model objects. + ## Call a Workers AI model Pass a `@cf/` id. Model ids autocomplete from `@cloudflare/workers-types`: @@ -71,11 +103,61 @@ export default { -Any string that is not a `@cf/` id throws a `TypeError` that names the provider package to install. +A string that is not a Workers AI id, a catalog id, or a dynamic route throws a `TypeError` that names the three forms. + +## Call an AI Gateway catalog model + +Pass an `/` id from the [model catalog](/ai/models/). No vendor package or key is needed: + + + +```ts +import { createAI } from "agents/models/ai-sdk"; +import { generateText } from "ai"; + +export default { + async fetch(request: Request, env: Env) { + const ai = createAI({ binding: env.AI, id: "my-gateway" }); + + const { text } = await generateText({ + model: ai("anthropic/claude-opus-5.5"), + prompt: "Say hello in three words", + }); + + return Response.json({ text }); + }, +}; +``` + + + +Use the catalog's spelling of the id, not the vendor's. AI Gateway looks the model up by its exact catalog name, so `anthropic/claude-opus-5.5` works and `anthropic/claude-opus-5-5` does not. Other examples are `openai/gpt-5.5` and `cloudflare/auto`. -## Call a third-party model +The request goes through `env.AI.run()` to AI Gateway. The provider sends it in the OpenAI chat completions format, and AI Gateway converts it to and from the format of the provider that serves the model. The gateway also handles: -Build the model with the vendor's own AI SDK provider, then pass it to `ai()`. The request goes through AI Gateway, which holds the credential, so the vendor's `apiKey` is a placeholder: +- **Credentials.** [Unified Billing](/ai-gateway/features/unified-billing/) bills the model to your Cloudflare account. A key you [store on the gateway](/ai-gateway/configuration/bring-your-own-keys/) is used instead when one is configured. +- **Providers.** A catalog model can have several providers. AI Gateway picks one and retries across them. +- **Errors.** An unknown model, an unpaid account, or an upstream failure throws a `CloudflareAIError`, the same type as a Workers AI failure. + +Unified Billing requires [authentication](/ai-gateway/configuration/authentication/) on the gateway. Without it, the request fails with error `2049`. + +`reasoningEffort` is sent as `reasoning_effort`. `chatTemplateKwargs` is a Workers AI option, so the provider drops it with a warning. + +## Call a dynamic route + +An [AI Gateway dynamic route](/ai-gateway/features/dynamic-routing/) is named `dynamic/`. The route you configured on the gateway picks the model, and can apply conditions, percentages, rate and budget limits, and its own fallbacks: + +```ts +const ai = createAI({ binding: env.AI, id: "my-gateway" }); + +await generateText({ model: ai("dynamic/support"), prompt: "Hi" }); +``` + +A route belongs to one gateway, so set the `id` of the gateway you created it on. The route answers in the chat completions format whatever model it picks. Like a catalog model, it drops `chatTemplateKwargs` with a warning. A dynamic route is a language model only. The embedding, image, speech, transcription, and reranking methods reject it. + +## Call a vendor's model object + +Build the model with the vendor's own AI SDK provider, then pass it to `ai()`. AI Gateway holds the credential, so the vendor's `apiKey` is a placeholder: @@ -92,7 +174,7 @@ export default { const openai = createOpenAI({ apiKey: "cloudflare" }); const claude = await generateText({ - model: ai(anthropic("claude-opus-4-8")), + model: ai(anthropic("claude-opus-5-5")), prompt: "Say hello in three words", }); @@ -108,21 +190,51 @@ export default { -Use the vendor's own model id. The provider does not rewrite ids, so `claude-opus-4.8` reaches Anthropic as written and Anthropic rejects it. +The vendor's package builds the request and parses the response. The provider replaces the model's `fetch`, and sends the same request, with the same method, headers, and body, through the binding to AI Gateway's [provider-native endpoint](/ai-gateway/usage/providers/) for that vendor: + +```txt +https://workers-binding.ai/ai-gateway/gateways/{gateway}/{provider}/{endpoint} +``` + +The provider takes the gateway provider from the request host. For example, `api.anthropic.com` is `anthropic` and `api.openai.com` is `openai`. A host that AI Gateway has no provider for throws a `TypeError`. Gateway options travel as `cf-aig-*` headers. Multipart bodies, such as file uploads for transcription, work. + +Use the vendor's own model id, such as `claude-opus-5-5`. The vendor's package reads model capabilities from it. Providers on [Unified Billing](/ai-gateway/features/unified-billing/) answer with no key of yours. For any other provider, [store a key on the gateway](/ai-gateway/configuration/bring-your-own-keys/). Until you do, the vendor's own error comes back unchanged. `ai()` clones the model for each call and never changes the object you passed. If you add middleware, wrap the model with `ai()` first: ```ts -wrapLanguageModel({ model: ai(anthropic("claude-opus-4-8")), middleware }); +wrapLanguageModel({ model: ai(anthropic("claude-opus-5-5")), middleware }); ``` A model that is already wrapped in middleware hides the settings the provider needs, so `ai()` rejects it. +### Unified billing and BYOK + +By default, the provider removes the vendor's credential header (`authorization`, `x-api-key`, `x-goog-api-key`, or `api-key`) so AI Gateway supplies the key. To use a key you hold, give it to the vendor's provider and set `vendorKey: "request"` on the model: + +```ts +const anthropic = createAnthropic({ apiKey: env.ANTHROPIC_API_KEY }); +const model = ai(anthropic("claude-opus-5-5"), { vendorKey: "request" }); +``` + +`vendorKey` belongs to the model you set it on. A fallback model does not inherit it, so one chain can mix a model on your key with a model on Unified Billing. + +`byokAlias` picks a [stored key by alias](/ai-gateway/configuration/bring-your-own-keys/) when the gateway holds several keys for one provider. The provider sends it as `cf-aig-byok-alias` for catalog models and model objects. + +`zdr` turns [Zero Data Retention](/ai-gateway/features/unified-billing/#zero-data-retention-zdr) on (`true`) or off (`false`) for one Unified Billing request, whatever the gateway's own setting. The provider sends it as `cf-aig-zdr`. It applies to model objects only, because catalog models and dynamic routes do not read it. + +```ts +const model = ai(anthropic("claude-opus-5-5"), { + byokAlias: "production", + zdr: true, +}); +``` + ## Stream, call tools, and return structured output -Both kinds of model work with every AI SDK call: +Every kind of model works with every AI SDK call: @@ -173,6 +285,24 @@ export default { +## Resume a dropped stream + +Through the binding, a stream from a catalog model or a dynamic route resumes after a dropped connection. You do not need to configure anything: + +```ts +const result = streamText({ + model: ai("anthropic/claude-opus-5.5"), + prompt: "Write a haiku about Durable Objects", +}); +// If the connection drops, result.textStream keeps going. +``` + +AI Gateway names each streamed run in the `cf-aig-run-id` header and keeps its events. If the connection drops mid-stream, the provider reconnects through the binding and replays the run from the last complete event. Your stream continues with nothing lost or repeated. The run keeps going on the gateway while the connection is down, so the resumed stream continues the same answer. + +The provider gives up and throws a `CloudflareAIError` after five drops in one stream, or once the gateway no longer holds the run. The gateway keeps a run for a few minutes after it ends. Aborting the call never resumes it. + +Workers AI `@cf/` streams and model objects have no run id, so they do not resume. The run id is in `providerMetadata.cloudflare.runId`. + ## Use it in Think Return a model from `getModel()`: @@ -221,7 +351,7 @@ Attach `metadata` to the gateway log entry. Set it once on the provider and add const ai = createAI({ binding: env.AI, metadata: { app: "support-bot" } }); await generateText({ - model: ai(anthropic("claude-opus-4-8")), + model: ai(anthropic("claude-opus-5-5")), prompt: "Summarize this ticket", providerOptions: { cloudflare: { metadata: { userId: "u_123" }, eventId: "ticket-42" }, @@ -233,19 +363,21 @@ await generateText({ ## Fall back to another model -`fallback` lists models to try in order if the primary model fails before it produces any output. Mix Workers AI ids and model objects: +`fallback` lists models to try in order if the primary model fails before it produces any output. A fallback model can be a Workers AI id, a catalog id, a dynamic route, or a model object: ```ts -const model = ai(anthropic("claude-opus-4-8"), { - fallback: [ai(openai.responses("gpt-5-mini")), "@cf/zai-org/glm-4.7-flash"], +const model = ai(anthropic("claude-opus-5-5"), { + fallback: ["openai/gpt-5.5", "dynamic/support", "@cf/zai-org/glm-4.7-flash"], }); ``` +A model object in the list goes to the provider-native endpoint, like `ai(model)`. + A request that the vendor rejects falls back. So does a stream that fails before its first output part. Once a model has produced output, its answer is final, errors included. `providerMetadata.cloudflare.model` names the model that answered. -Each fallback model uses the chain's gateway options. The chain's `id`, `cacheTtl`, and the other gateway options win over a fallback model's own, and `metadata` merges. Headers and Workers AI options a fallback model was built with stay its own. +Each fallback model uses the chain's gateway options. The chain's `id`, `cacheTtl`, and the other gateway options win over a fallback model's own, and `metadata` merges. The `headers`, Workers AI options, and `vendorKey` a fallback model was built with stay its own. -Fallback runs in your Worker today. +Fallback runs in your Worker and moves across models. Within one catalog model, AI Gateway already retries across that model's providers, and a dynamic route runs its own fallback on the gateway. ## Set options @@ -256,7 +388,7 @@ Set gateway options on the provider, on a model, or per call under `providerOpti const ai = createAI({ binding: env.AI, id: "prod", metadata: { app: "demo" } }); // Model -const model = ai(anthropic("claude-opus-4-8"), { cacheTtl: 3600 }); +const model = ai("anthropic/claude-opus-5.5", { cacheTtl: 3600 }); // Call await generateText({ @@ -272,25 +404,31 @@ The provider removes `providerOptions.cloudflare` before the call reaches a vend Gateway options can be given flat or nested: `{ cacheTtl: 60 }` and `{ gateway: { cacheTtl: 60 } }` are the same. -| Option | Type | Description | -| -------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------ | -| `id` | `string` | Gateway id. Defaults to `"default"`, which is created the first time you use it. | -| `gateway` | `string \| GatewayOptions` | The gateway id, or the gateway options nested. | -| `skipCache` | `boolean` | Bypass the gateway cache. | -| `cacheTtl` | `number` | Cache lifetime in seconds. | -| `cacheKey` | `string` | Cache key to use instead of the one derived from the request. | -| `collectLog` | `boolean` | Whether the gateway stores the request and response in its log. | +| Option | Type | Description | +| -------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `binding` | `Ai` | The `AI` binding. Provider only. | +| `accountId` | `string` | The account for an [account id and API token](#use-an-account-id-and-api-token). Provider only. | +| `apiToken` | `string` | A Cloudflare API token for that account. Provider only. | +| `id` | `string` | Gateway id. Defaults to `"default"`, which is created the first time you use it. | +| `gateway` | `string \| GatewayOptions` | The gateway id, or the gateway options nested. | +| `skipCache` | `boolean` | Bypass the gateway cache. | +| `cacheTtl` | `number` | Cache lifetime in seconds. | +| `cacheKey` | `string` | Cache key to use instead of the one derived from the request. | +| `collectLog` | `boolean` | Whether the gateway stores the request and response in its log. | | `metadata` | `Record` | Values attached to the gateway log entry. | -| `eventId` | `string` | Groups several requests into one gateway event. | -| `requestTimeoutMs` | `number` | Timeout for the upstream request. | -| `retries` | `{ maxAttempts, retryDelayMs, backoff }` | Gateway retry policy for the upstream call. `maxAttempts` is 1 to 5. | -| `fallback` | `(WorkersAIModelId \| LanguageModelV4)[]` | Models to try in order. Model and call only. | -| `headers` | `Record` | Extra request headers, merged last. Model and call only. | -| `sessionAffinity` | `string` | Workers AI only. Sends requests with the same key to one replica for prefix caching. | -| `reasoningEffort` | `"low" \| "medium" \| "high" \| null` | Workers AI only. `null` turns reasoning off on models that allow it. | -| `chatTemplateKwargs` | `Record` | Workers AI only. Sent as `chat_template_kwargs`, for example `{ enable_thinking: false }`. | - -For a vendor's model, ask for reasoning with the AI SDK's `reasoning` call option or the vendor's own `providerOptions`. +| `eventId` | `string` | Groups several requests into one gateway event. | +| `requestTimeoutMs` | `number` | Timeout for the upstream request. | +| `retries` | `{ maxAttempts, retryDelayMs, backoff }` | Gateway retry policy for the upstream call. `maxAttempts` is 1 to 5. | +| `byokAlias` | `string` | A stored BYOK key, by alias. Catalog models and model objects. | +| `zdr` | `boolean` | Zero Data Retention on or off for a Unified Billing request. Model objects only. | +| `vendorKey` | `"gateway" \| "request"` | `"request"` forwards the vendor key on the request. Model objects only, per model. | +| `fallback` | `(CloudflareLanguageModelId \| LanguageModelV4)[]` | Models to try in order. Model and call only. | +| `headers` | `Record` | Extra request headers, merged last. Model and call only. | +| `sessionAffinity` | `string` | Workers AI only. Sends requests with the same key to one replica for prefix caching. | +| `reasoningEffort` | `"low" \| "medium" \| "high" \| null` | Sent as `reasoning_effort`. `null` turns reasoning off on models that allow it. | +| `chatTemplateKwargs` | `Record` | Workers AI only. Sent as `chat_template_kwargs`, for example `{ enable_thinking: false }`. | + +`reasoningEffort` applies to Workers AI models, catalog models, and dynamic routes. When it is not set, the AI SDK's `reasoning` call option maps onto it. For a model object, ask for reasoning with the AI SDK's `reasoning` call option or the vendor's own `providerOptions`. The provider ignores `reasoningEffort` and `chatTemplateKwargs` on a model object and adds a warning. ## Read gateway metadata @@ -298,13 +436,13 @@ Every result, and every stream's `finish` part, carries `providerMetadata.cloudf ```ts const { providerMetadata } = await generateText({ - model: ai(anthropic("claude-opus-4-8")), + model: ai(anthropic("claude-opus-5-5")), prompt: "hi", }); providerMetadata?.cloudflare; // { -// model: "claude-opus-4-8", +// model: "claude-opus-5-5", // provider: "anthropic", // gateway: "default", // logId: "01M1KZWN069WWNPC18V05NKHSS", @@ -313,24 +451,24 @@ providerMetadata?.cloudflare; // } ``` -| Field | Description | -| ------------- | -------------------------------------------------------------- | -| `model` | The model that answered, after any fallback. | -| `gateway` | The gateway id the request went through. | -| `provider` | The gateway provider, for a vendor's model. | -| `logId` | The gateway log entry, from `cf-aig-log-id`. | -| `eventId` | The gateway event, from `cf-aig-event-id`. | -| `requestId` | The gateway request id, from `cf-aig-request-id`. | -| `traceId` | The trace this request belongs to, from `cf-aig-trace-id`. | -| `runId` | From `cf-aig-run-id`, when the gateway sends one. | -| `cacheStatus` | `HIT` or `MISS`, from `cf-aig-cache-status`. | -| `step` | The step of a gateway route that answered, from `cf-aig-step`. | - -Only `model` and `gateway` are always present. Check for the other fields before you use them. +| Field | Description | +| ------------- | ------------------------------------------------------------------------------- | +| `model` | The model that answered, after any fallback. | +| `gateway` | The gateway id, when a gateway handled the request. | +| `provider` | The gateway provider, for a model object. | +| `logId` | The gateway log entry, from `cf-aig-log-id`. | +| `eventId` | The gateway event, from `cf-aig-event-id`. | +| `requestId` | The gateway request id, from `cf-aig-request-id`. | +| `traceId` | The trace this request belongs to, from `cf-aig-trace-id`. | +| `runId` | The streamed run, from `cf-aig-run-id`, for a catalog model or a dynamic route. | +| `cacheStatus` | `HIT` or `MISS`, from `cf-aig-cache-status`. | +| `step` | The step of a gateway route that answered, from `cf-aig-step`. | + +Only `model` is always present. Check for the other fields before you use them. ## Handle errors -The provider throws `CloudflareAIError`, which extends the AI SDK's `APICallError`. The AI SDK's retry loop reads its `isRetryable` flag: +The provider throws `CloudflareAIError`, which extends the AI SDK's `APICallError`, for Workers AI failures, AI Gateway failures, failed stream resumes, and a fallback chain where every model failed. The AI SDK's retry loop reads its `isRetryable` flag: ```ts import { CloudflareAIError } from "agents/models/ai-sdk"; @@ -347,7 +485,7 @@ try { `code` is one of `"auth"`, `"rate-limit"`, `"not-found"`, `"bad-request"`, `"provider-error"`, `"gateway-error"`, or `"unknown"`. -For a vendor's model, `CloudflareAIError` covers the gateway's own failures, such as a missing gateway or an unpaid account. The vendor's own errors, such as an unknown model id or a rate limit, come from the vendor's provider in its own error type. +For a model object, `CloudflareAIError` covers the gateway's own failures, such as a missing gateway or an unpaid account. The vendor's own errors, such as an unknown model id or a rate limit, come from the vendor's provider in its own error type. ## Use other modalities @@ -398,9 +536,9 @@ Model-specific settings go under `providerOptions.cloudflare`: | Transcription | `audio`, `mediaType` | `language`, `task`, `initialPrompt`, `prefix`, `vadFilter` | | Reranking | `query`, `documents`, `topN` (as `top_k`) | None | -An option a model does not take is listed in the result's `warnings` instead of being sent. For example, `flux-1-schnell` takes only a prompt and `steps`. Image generation makes one image per request, so `generateImage` with a larger `n` sends parallel requests. +An option a model does not take is listed in the result's `warnings` instead of being sent. For example, `flux-1-schnell` takes only a prompt and `steps`, and `@cf/deepgram/nova-3` takes `language` but not the Whisper-only transcription options. Image generation makes one image per request, so `generateImage` with a larger `n` sends parallel requests. -To route a vendor's own embedding, image, speech, or transcription model through AI Gateway, pass it to `ai.routed(model)`. `ai.routed()` takes the gateway options, `headers`, and `sessionAffinity`. +These methods take Workers AI `@cf/` ids only, including in `fallback`. To route a vendor's own embedding, image, speech, or transcription model through AI Gateway, pass it to `ai.routed(model)`. It goes to the provider-native endpoint, like `ai(model)`. `ai.routed()` takes the gateway options, `headers`, `sessionAffinity`, and `vendorKey`. ## Use a provider registry @@ -411,7 +549,7 @@ import { createProviderRegistry, generateText } from "ai"; const registry = createProviderRegistry({ cloudflare: ai }); await generateText({ - model: registry.languageModel("cloudflare:@cf/zai-org/glm-4.7-flash"), + model: registry.languageModel("cloudflare:anthropic/claude-opus-5.5"), prompt: "Hi", }); @@ -420,17 +558,20 @@ globalThis.AI_SDK_DEFAULT_PROVIDER = createAI({ binding: env.AI }); await generateText({ model: "@cf/zai-org/glm-4.7-flash", prompt: "Hi" }); ``` -A string still has to be a `@cf/` id. Reach a vendor's model with a model object. +A string can be a Workers AI id, a catalog id, or a dynamic route. ## Limitations -- AI Gateway's universal request carries JSON only. A multipart body, such as a file upload sent as `FormData`, throws a `TypeError`. -- `@cf/deepgram/nova-3` and `@cf/deepgram/flux` are not supported. They throw a `CloudflareAIError` before any request is sent. Use `@cf/openai/whisper-large-v3-turbo` for transcription. -- `reasoningEffort`, `chatTemplateKwargs`, and `sessionAffinity` apply to Workers AI models only. +- `@cf/deepgram/flux` takes websocket connections only, so it throws a `CloudflareAIError` before any request is sent. Use `@cf/deepgram/nova-3` or `@cf/openai/whisper-large-v3-turbo` for transcription. +- `chatTemplateKwargs` and `sessionAffinity` apply to Workers AI models only. +- `zdr` applies to model objects only. +- Workers AI streams and model object streams do not resume after a dropped connection. ## Related resources - [pi-ai provider](/agents/models/pi-ai/) - [Model catalog](/ai/models/) +- [Dynamic routing](/ai-gateway/features/dynamic-routing/) +- [AI Gateway provider-native endpoints](/ai-gateway/usage/providers/) - [AI Gateway Worker binding methods](/ai-gateway/usage/worker-binding-methods/) - [Unified Billing](/ai-gateway/features/unified-billing/) diff --git a/src/content/docs/agents/models/index.mdx b/src/content/docs/agents/models/index.mdx index d62bfd3ae84..62e6bf1159d 100644 --- a/src/content/docs/agents/models/index.mdx +++ b/src/content/docs/agents/models/index.mdx @@ -1,7 +1,7 @@ --- title: Models pcx_content_type: overview -description: Call Workers AI and third-party models through AI Gateway from the AI SDK or Pi AI with one createAI provider over the AI binding. +description: Call Workers AI, AI Gateway catalog models, dynamic routes, and vendor models from the AI SDK or Pi AI with one createAI provider over the AI binding or an account id and API token. tags: - AI sidebar: @@ -15,14 +15,14 @@ products: import { LinkCard, WranglerConfig } from "~/components"; -The Agents SDK includes model providers for [Workers AI](/workers-ai/) and [AI Gateway](/ai-gateway/). Each provider is one `createAI` factory over the Worker's `AI` binding. There are two, one per framework: +The Agents SDK includes model providers for [Workers AI](/workers-ai/) and [AI Gateway](/ai-gateway/). Each provider is one `createAI` factory over the Worker's `AI` binding, or an account id and API token. There are two, one per framework: | Import | Framework | Use it with | | ---------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------- | | `agents/models/ai-sdk` | [AI SDK](https://ai-sdk.dev/) v7 | `generateText`, `streamText`, Think, and any AI SDK consumer | | `agents/models/pi-ai` | [Pi AI](https://github.com/earendil-works/pi/tree/main/packages/ai) | The [Pi harness](/agents/harnesses/pi/), and Pi AI's `stream` | -Both take the same options, route through the same gateway, and handle Workers AI the same way. +Both take the same options, route through the same gateway, and handle Workers AI the same way. The [OpenCode harness](/agents/harnesses/opencode/) has a third, `agents/models/opencode`, built on the AI SDK provider. :::caution[Beta] The model providers are in beta. Their APIs may change in a minor release. @@ -30,23 +30,35 @@ The model providers are in beta. Their APIs may change in a minor release. ## How a model is chosen -The providers keep one model catalog up to date: Workers AI. Every other vendor's models come from that vendor's own package, and the provider routes them through AI Gateway. +Name a model with a string, or pass a model object built by the vendor's own package: -- **A Workers AI model** is a `@cf/` id, such as `ai("@cf/zai-org/glm-4.7-flash")`. It runs through `env.AI.run()`. A compatibility layer turns each model's response into the standard OpenAI chat completions shape. -- **A third-party model** is a model object built by the vendor's provider, such as `ai(anthropic("claude-opus-4-8"))`. The vendor's code builds the request and parses the response. The provider swaps the transport, so the request goes through AI Gateway with `env.AI.gateway(id).run()`. +```ts +ai("@cf/zai-org/glm-4.7-flash"); // Workers AI +ai("anthropic/claude-opus-5.5"); // AI Gateway catalog +ai("dynamic/support"); // AI Gateway dynamic route +ai(anthropic("claude-opus-5-5")); // the vendor's own model object +``` + +- **A Workers AI model** is a `@cf/` id. It runs through `env.AI.run()`. A compatibility layer turns each model's response into the standard OpenAI chat completions shape. +- **An AI Gateway catalog model** is an `/` id from the [model catalog](/ai/models/), such as `anthropic/claude-opus-5.5` or `openai/gpt-5.5`. It also runs through `env.AI.run()`, which sends it to AI Gateway. The gateway picks a provider and bills the call. Use the catalog's spelling, not the vendor's: `anthropic/claude-opus-5.5`, not `claude-opus-5-5`. +- **A [dynamic route](/ai-gateway/features/dynamic-routing/)** is `dynamic/`. The route you configured on the gateway picks the model. +- **A model object** comes from the vendor's provider package, such as `anthropic("claude-opus-5-5")`. The vendor's code builds the request and parses the response. The provider sends that request unchanged through the binding to AI Gateway's [provider-native endpoint](/ai-gateway/usage/providers/) for that vendor. -The provider does not keep third-party model ids, wire formats, or thinking settings. When a vendor ships a new model, update the vendor's package. +The providers do not use the deprecated [Universal Endpoint](/ai-gateway/usage/universal/). ## What every model gets -- **No API tokens in your Worker.** Requests go through the `AI` binding. AI Gateway holds third-party credentials, through [Unified Billing](/ai-gateway/features/unified-billing/) or a key you [store on the gateway](/ai-gateway/configuration/bring-your-own-keys/). -- **Gateway options.** Caching, logging, metadata, timeouts, and retries are options on the provider, the model, or the call. The `default` gateway is created the first time you use it. -- **Fallback.** List models to try in order if the first fails before it produces output. Mix Workers AI and third-party models freely. -- **Gateway metadata on every result.** Each response carries the gateway log id, cache status, and the model that actually answered. +- **No vendor keys.** AI Gateway holds third-party credentials, through [Unified Billing](/ai-gateway/features/unified-billing/) or a key you [store on the gateway](/ai-gateway/configuration/bring-your-own-keys/). Unified Billing requires [authentication](/ai-gateway/configuration/authentication/) on the gateway. +- **Gateway options.** Caching, logging, metadata, timeouts, retries, and BYOK key aliases are options on the provider, the model, or the call. The `default` gateway is created the first time you use it. +- **Fallback.** List models to try in order if the first fails before it produces output. Mix Workers AI ids, catalog ids, dynamic routes, and model objects. +- **Stream resume.** Through the binding, a stream from a catalog model or a dynamic route resumes after a dropped connection. You do not need to configure anything. +- **Gateway metadata on every result.** Each response carries the gateway log id, cache status, run id, and the model that actually answered. + +`createAI` from `agents/models/ai-sdk` replaces `workers-ai-provider` and `ai-gateway-provider`. For AI Search, use `ai-search-provider`. -## Configure the binding +## Connect to Cloudflare -Both providers need the `AI` binding: +Inside a Worker, pass the `AI` binding, `createAI({ binding: env.AI })`, and add it to your Wrangler configuration: @@ -60,6 +72,16 @@ Both providers need the `AI` binding: +To use a gateway in another account, or to call from outside a Worker, pass an account id and API token instead. [Create the token](/fundamentals/api/get-started/create-token/) with **Workers AI Read** and **AI Gateway Run**: + +```ts +const ai = createAI({ + accountId: "", + apiToken: env.CLOUDFLARE_API_TOKEN, + gateway: "my-gateway", +}); +``` + ## Choose a provider @@ -43,6 +43,18 @@ Add the `AI` binding to your Wrangler configuration: +To use a gateway in another account, or to call from outside a Worker, pass `accountId` and `apiToken` instead of `binding`. Everything else is the same, and the gateway still defaults to `default`: + +```ts +const ai = createAI({ + accountId: "", + apiToken: env.CLOUDFLARE_API_TOKEN, + gateway: "my-gateway", +}); +``` + +[Create the API token](/fundamentals/api/get-started/create-token/) with **Workers AI Read** and **AI Gateway Run**. The token is sent to the AI Gateway REST API and to `gateway.ai.cloudflare.com` only. + ## Call a model The provider carries Pi AI's entry points, so nothing is registered globally: @@ -76,7 +88,7 @@ export default { `ai.stream()` returns Pi AI's `AssistantMessageEvent` stream. `ai.completeSimple()` and `ai.streamSimple()` take Pi AI's simple options, such as a `reasoning` level and `maxTokens`: ```ts -const model = ai("anthropic/claude-opus-4.8"); +const model = ai("anthropic/claude-opus-5.5"); const context = { messages: [{ role: "user", content: "Hi", timestamp: Date.now() }], }; @@ -92,30 +104,37 @@ await ai.completeSimple(model, context, { reasoning: "low" }); ## Name a model -There are three ways to name a model: +There are four ways to name a model: ```ts import { createAI } from "agents/models/pi-ai"; import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all"; -const ai = createAI({ binding: env.AI }); +const ai = createAI({ binding: env.AI, id: "my-gateway" }); // 1. A Workers AI model, by its @cf/ id. ai("@cf/zai-org/glm-4.7-flash"); -// 2. A model from pi-ai's Cloudflare AI Gateway catalog, by /. -ai("anthropic/claude-opus-4.8"); +// 2. An AI Gateway dynamic route, by dynamic/. +ai("dynamic/support"); + +// 3. A model from Pi AI's Cloudflare AI Gateway catalog, by /. +ai("anthropic/claude-opus-5.5"); ai("openai/gpt-5.4"); -// 3. Any pi-ai model object, from any pi-ai registry. -ai(getBuiltinModel("anthropic", "claude-opus-4-8")); +// 4. Any Pi AI model object, from any Pi AI registry. +ai(getBuiltinModel("anthropic", "claude-opus-5-5")); ``` Any other string throws a `TypeError`. The provider does not guess what a prefix means. -The second and third forms keep Pi AI's model metadata, including `api`, `compat`, `thinkingLevelMap`, cost, context window, and maximum tokens. That metadata decides how each vendor is asked to think. For example, Claude Opus gets `thinking: { type: "adaptive" }` and GPT-5 gets `reasoning.effort`. `ai(model)` does not change the object you pass. +A [dynamic route](/ai-gateway/features/dynamic-routing/) runs on `env.AI.run()` under the gateway you created it on, so set that gateway's `id`. The route picks the model and answers in the chat completions format. It takes the same metadata defaults and overrides as a Workers AI id. + +A catalog id uses the AI Gateway catalog's spelling, `anthropic/claude-opus-5.5`. Pi AI's own list spells Claude ids the way Anthropic does (`claude-opus-5-5`), so the provider lists them under the catalog spelling. A model object from a vendor registry keeps the vendor's spelling. -Third-party models go through AI Gateway, which holds the credential through [Unified Billing](/ai-gateway/features/unified-billing/) or a [key stored on the gateway](/ai-gateway/configuration/bring-your-own-keys/). Your Worker holds no provider key. +The third and fourth forms keep Pi AI's model metadata, including `api`, `compat`, `thinkingLevelMap`, cost, context window, and maximum tokens. That metadata decides how each vendor is asked to think. For example, Claude Opus gets `thinking: { type: "adaptive" }` and GPT-5 gets `reasoning.effort`. `ai(model)` does not change the object you pass. + +Third-party models go through AI Gateway, which holds the credential through [Unified Billing](/ai-gateway/features/unified-billing/) or a [key stored on the gateway](/ai-gateway/configuration/bring-your-own-keys/). Your Worker holds no provider key. Unified Billing requires [authentication](/ai-gateway/configuration/authentication/) on the gateway. ## Use it with the Pi harness @@ -133,12 +152,12 @@ models.setProvider(ai.provider); // defaults: { model: ai("@cf/moonshotai/kimi-k2.7-code") } // For one session: -await session.setModel(ai("anthropic/claude-opus-4.8")); +await session.setModel(ai("anthropic/claude-opus-5.5")); ``` -A Pi session stores only its model's provider and id, then looks the model up in the `Models` registry. Use the `@cf/` and `/` forms there: both belong to the `cloudflare` provider (`CLOUDFLARE_PROVIDER_ID`) that you registered. A model object keeps its vendor's provider id, which this registry does not know. Options passed to `ai(model, options)` do not reach a session. Set them on `createAI` instead. +A Pi session stores only its model's provider and id, then looks the model up in the `Models` registry. Use the `@cf/`, `dynamic/`, and `/` forms there. They belong to the `cloudflare` provider (`CLOUDFLARE_PROVIDER_ID`) that you registered. A model object keeps its vendor's provider id, which this registry does not know. Options passed to `ai(model, options)` do not reach a session. Set them on `createAI` instead. -The provider's model list is every Workers AI model Pi AI knows, plus every `/` in Pi AI's AI Gateway catalog, so `models.getModel("cloudflare", "anthropic/claude-opus-4.8")` resolves. +The provider's model list is every Workers AI model Pi AI knows, plus every `/` in Pi AI's AI Gateway catalog, so `models.getModel("cloudflare", "anthropic/claude-opus-5.5")` resolves. ## Cache, tag, and time out requests @@ -147,17 +166,20 @@ Gateway options go on `createAI`, on a model, or on a call. They can be flat or ```ts const ai = createAI({ binding: env.AI, id: "prod", cacheTtl: 60 }); -const model = ai("anthropic/claude-opus-4.8", { +const model = ai("anthropic/claude-opus-5.5", { cacheTtl: 3600, cacheKey: "greeting-v1", metadata: { team: "growth" }, eventId: "turn-17", requestTimeoutMs: 30_000, retries: { maxAttempts: 3, backoff: "exponential", retryDelayMs: 500 }, + byokAlias: "production", }); ``` -The options are the same as the [AI SDK provider's options](/agents/models/ai-sdk/#set-options): `id`, `skipCache`, `cacheTtl`, `cacheKey`, `collectLog`, `metadata`, `eventId`, `requestTimeoutMs`, `retries`, `fallback`, `headers`, `sessionAffinity`, `reasoningEffort`, and `chatTemplateKwargs`. +The options are the same as the [AI SDK provider's options](/agents/models/ai-sdk/#set-options): `id`, `skipCache`, `cacheTtl`, `cacheKey`, `collectLog`, `metadata`, `eventId`, `requestTimeoutMs`, `retries`, `byokAlias`, `zdr`, `fallback`, `headers`, `sessionAffinity`, `reasoningEffort`, and `chatTemplateKwargs`. `vendorKey` is AI SDK only. + +`byokAlias` picks a [stored key by alias](/ai-gateway/configuration/bring-your-own-keys/) when the gateway holds several keys for one provider. The provider sends it as `cf-aig-byok-alias` for catalog models and model objects. `zdr` turns [Zero Data Retention](/ai-gateway/features/unified-billing/#zero-data-retention-zdr) on or off for a Unified Billing request. It applies only to model objects outside Pi AI's gateway catalog, because catalog models do not read it. Pi AI's own call options map onto them. `headers` merge last, `metadata` joins the gateway metadata, `sessionId` sets session affinity, and `reasoning` maps through the model's own `thinkingLevelMap`. A call option wins over a model option, which wins over `createAI`. @@ -168,12 +190,12 @@ A Pi AI model is plain data. Per-model options are stored on it under symbol key ## Fall back to another model ```ts -ai(getBuiltinModel("anthropic", "claude-opus-4-8"), { +ai(getBuiltinModel("anthropic", "claude-opus-5-5"), { fallback: ["@cf/zai-org/glm-4.7-flash"], }); ``` -Fallback models can be Workers AI ids, `/` strings, or model objects. The provider tries them in order. It moves on when a stream ends with an `error` before producing any output. Once a model has produced output, its answer is final. +Fallback models can be Workers AI ids, dynamic routes, `/` strings, or model objects. The provider tries them in order. It moves on when a stream ends with an `error` before producing any output. Once a model has produced output, its answer is final. The message that answers has a `cloudflare-fallback` diagnostic listing the attempts that failed, and `message.model` names the model that answered. @@ -196,20 +218,32 @@ A failed request ends the stream with an `error` event. Its message has a `cloud The gateway's own failures, such as a missing gateway or an unpaid account, become a `CloudflareAIError`. Errors from the vendor come back as the vendor sent them. +## Resume a dropped stream + +Through the binding, a stream from a catalog model or a dynamic route resumes after a dropped connection. You do not need to configure anything. + +AI Gateway names each streamed run in the `cf-aig-run-id` header and keeps its events. If the connection drops, the provider reconnects through the binding and replays the run from the last complete event. The stream continues with nothing lost or repeated. + +The provider gives up after five drops in one stream, or once the gateway no longer holds the run. The gateway keeps a run for a few minutes after it ends. Aborting the call never resumes it. Workers AI `@cf/` streams and model objects outside the catalog have no run id, so they do not resume. The run id is on the `cloudflare` diagnostic as `runId`. + ## How requests are routed -The provider routes by the model's Pi AI `api`: +The provider routes every request through the `AI` binding, or over HTTP with an account id and API token: + +| Model | `api` | Through the binding | With an account id and token | +| ---------------------------------------------------------- | -------------------- | -------------------------------------------------------------- | ------------------------------------------------------------- | +| `@cf/` models and dynamic routes | `cloudflare-ai` | `env.AI.run()`, with Cloudflare's compatibility layer | The AI Gateway REST API, with the same compatibility layer | +| Anthropic models from Pi AI's gateway catalog | `anthropic-messages` | `env.AI.run()` to AI Gateway, in Anthropic Messages format | The REST API's `/ai/v1/messages`, in the same format | +| OpenAI models from Pi AI's gateway catalog | `openai-responses` | `env.AI.run()` to AI Gateway, in OpenAI Responses format | The REST API's `/ai/v1/responses`, in the same format | +| Any other model object, such as Anthropic, OpenAI, or Groq | its own `api` | The binding's `fetch` to AI Gateway's provider-native endpoint | `gateway.ai.cloudflare.com`, the provider-native endpoint | + +A model from Pi AI's Cloudflare AI Gateway catalog runs on `env.AI.run()` under its catalog id, in the model's native format. AI Gateway converts nothing, so Pi AI's own stream processors read the answer. The gateway picks the provider and bills through Unified Billing or a key stored on the gateway. -| Model | `api` | Transport | -| ----------------------------- | -------------------- | ----------------------------------------------------- | -| `@cf/` models | `cloudflare-ai` | `env.AI.run()`, with Cloudflare's compatibility layer | -| Anthropic, from any registry | `anthropic-messages` | AI Gateway universal request to `v1/messages` | -| OpenAI, from any registry | `openai-responses` | AI Gateway universal request to `v1/responses` | -| Groq, DeepSeek, xAI, and more | `openai-completions` | AI Gateway universal request to `v1/chat/completions` | +Any other model object goes to AI Gateway's [provider-native endpoint](/ai-gateway/usage/providers/) through the binding's `fetch`. The provider takes the gateway provider from the model's `baseUrl` host. Pi AI's own converter builds the vendor's request body, and Pi AI parses the vendor's response stream. -For a third-party model, Pi AI's own converter builds the vendor's request body. The provider sends it to AI Gateway, and Pi AI parses the vendor's response stream. The provider never rewrites the model's `api`, `provider`, or `id`, so thinking signatures and encrypted reasoning items work on the next turn. +Either way, no vendor credential leaves your Worker. The provider never rewrites the model's `api`, `provider`, or `id`, so thinking signatures and encrypted reasoning items work on the next turn. -These four `api` values are the only ones the provider routes. Models from several Pi AI registries use other values and cannot be routed yet, including `mistral-conversations`, `google-generative-ai`, `google-vertex`, `amazon-bedrock`, `azure-openai-responses`, and `openai-codex`. Such a model's stream ends with an `error` event that names its `api`. +The provider routes four `api` values: `cloudflare-ai`, `anthropic-messages`, `openai-responses`, and `openai-completions`. Models from several Pi AI registries use other values and cannot be routed yet, including `mistral-conversations`, `google-generative-ai`, `google-vertex`, `amazon-bedrock`, `azure-openai-responses`, and `openai-codex`. Such a model's stream ends with an `error` event that names its `api`. So does a model whose `baseUrl` host AI Gateway has no provider for. On the `openai-completions` wire, the provider sends reasoning only as OpenAI's `reasoning_effort`. A model whose `compat.thinkingFormat` is anything else, such as `deepseek`, `qwen`, or `openrouter`, has its reasoning level dropped. A `cloudflare-compat` diagnostic on the message records the drop. Use Pi AI's own provider for that vendor if you need its thinking format. @@ -233,4 +267,5 @@ const result = await ai.generateImages(images, { - [AI SDK provider](/agents/models/ai-sdk/) - [Pi harness](/agents/harnesses/pi/) - [Model catalog](/ai/models/) +- [Dynamic routing](/ai-gateway/features/dynamic-routing/) - [Pi AI](https://github.com/earendil-works/pi/tree/main/packages/ai) diff --git a/src/content/docs/agents/runtime/communication/http-sse.mdx b/src/content/docs/agents/runtime/communication/http-sse.mdx index cda13eb8cda..860023ce43c 100644 --- a/src/content/docs/agents/runtime/communication/http-sse.mdx +++ b/src/content/docs/agents/runtime/communication/http-sse.mdx @@ -118,7 +118,7 @@ The [AI SDK](https://ai-sdk.dev/) provides built-in SSE streaming: ```ts import { Agent } from "agents"; import { streamText } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; interface Env { AI: Ai; @@ -128,10 +128,10 @@ export class ChatAgent extends Agent { async onRequest(request: Request): Promise { const { prompt } = await request.json<{ prompt: string }>(); - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), prompt: prompt, }); diff --git a/src/content/docs/agents/runtime/operations/using-ai-models.mdx b/src/content/docs/agents/runtime/operations/using-ai-models.mdx index 13a93257a05..9e5d8ff5066 100644 --- a/src/content/docs/agents/runtime/operations/using-ai-models.mdx +++ b/src/content/docs/agents/runtime/operations/using-ai-models.mdx @@ -39,7 +39,7 @@ Modern reasoning models can take some time to both generate a response _and_ str ```ts import { Agent } from "agents"; import { streamText } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; interface Env { AI: Ai; @@ -57,9 +57,9 @@ export class MyAgent extends Agent { async queryReasoningModel(connection: Connection, userPrompt: string) { try { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), prompt: userPrompt, }); @@ -183,16 +183,16 @@ Visit the [AI Gateway documentation](/ai-gateway/) to learn how to configure a g ## AI SDK -The [AI SDK](https://ai-sdk.dev/docs/introduction) provides a unified API for text generation, tool calling, structured responses, and more. It works with any provider that has an AI SDK adapter, including Workers AI via [`workers-ai-provider`](https://www.npmjs.com/package/workers-ai-provider). +The [AI SDK](https://ai-sdk.dev/docs/introduction) provides a unified API for text generation, tool calling, structured responses, and more. It works with any provider that has an AI SDK adapter, including Workers AI and AI Gateway through [`createAI`](/agents/models/ai-sdk/) from `agents/models/ai-sdk`. - + ```ts import { Agent } from "agents"; import { generateText } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; interface Env { AI: Ai; @@ -200,9 +200,9 @@ interface Env { export class MyAgent extends Agent { async onRequest(request: Request): Promise { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const { text } = await generateText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), prompt: "Build me an AI agent on Cloudflare Workers", }); diff --git a/src/content/docs/agents/tools/browser.mdx b/src/content/docs/agents/tools/browser.mdx index 8bc6f0d7305..04d906b59e8 100644 --- a/src/content/docs/agents/tools/browser.mdx +++ b/src/content/docs/agents/tools/browser.mdx @@ -344,11 +344,11 @@ Create browser tools with the Browser Run and Worker Loader bindings, then pass import { AIChatAgent } from "@cloudflare/ai-chat"; import { createBrowserTools } from "agents/browser/ai"; import { streamText, convertToModelMessages, stepCountIs } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; export class BrowserAgent extends AIChatAgent { async onChatMessage() { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const browserTools = createBrowserTools({ ctx: this.ctx, browser: this.env.BROWSER, @@ -356,7 +356,7 @@ export class BrowserAgent extends AIChatAgent { }); const result = streamText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), system: "You can inspect web pages with browser tools.", messages: await convertToModelMessages(this.messages), tools: browserTools, diff --git a/src/content/docs/agents/tools/codemode/browser.mdx b/src/content/docs/agents/tools/codemode/browser.mdx index 6429e787f97..94d293b855a 100644 --- a/src/content/docs/agents/tools/codemode/browser.mdx +++ b/src/content/docs/agents/tools/codemode/browser.mdx @@ -157,7 +157,7 @@ The browser creates the Code Mode tool and registers it as a dynamic client tool ```ts import { AIChatAgent, createToolsFromClientSchemas } from "@cloudflare/ai-chat"; import { convertToModelMessages, stepCountIs, streamText } from "ai"; - import { createWorkersAI } from "workers-ai-provider"; + import { createAI } from "agents/models/ai-sdk"; export class BrowserCodemode extends AIChatAgent { async onChatMessage( @@ -166,10 +166,10 @@ The browser creates the Code Mode tool and registers it as a dynamic client tool clientTools?: Parameters[0]; } ) { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/moonshotai/kimi-k2.7-code"), + model: ai("@cf/moonshotai/kimi-k2.7-code"), system: "Use the codemode tool to write JavaScript that calls browser-provided tools.", messages: await convertToModelMessages(this.messages), diff --git a/src/content/docs/agents/tools/filesystem.mdx b/src/content/docs/agents/tools/filesystem.mdx index f8a1cf1d3e7..9e6471eca72 100644 --- a/src/content/docs/agents/tools/filesystem.mdx +++ b/src/content/docs/agents/tools/filesystem.mdx @@ -175,14 +175,14 @@ import { Workspace } from "@cloudflare/computer"; import { createAITools } from "@cloudflare/computer/tools/ai-sdk"; import { generateText, stepCountIs } from "ai"; import { DurableObject } from "cloudflare:workers"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; export class WriterAgent extends DurableObject { workspace = new Workspace({ storage: this.ctx.storage }); async write(prompt: string) { const { text } = await generateText({ - model: createWorkersAI({ binding: this.env.AI })( + model: createAI({ binding: this.env.AI })( "@cf/moonshotai/kimi-k2.6", ), system: diff --git a/src/content/docs/agents/tools/mcp.mdx b/src/content/docs/agents/tools/mcp.mdx index 5d73ff761b0..9f9e9904206 100644 --- a/src/content/docs/agents/tools/mcp.mdx +++ b/src/content/docs/agents/tools/mcp.mdx @@ -30,7 +30,7 @@ Call `addMcpServer()` to connect to a remote MCP server, then pass `this.mcp.get ```ts import { Agent } from "agents"; import { generateText } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; export class ToolAgent extends Agent { async onStart() { @@ -38,10 +38,10 @@ export class ToolAgent extends Agent { } async onRequest(request: Request) { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const response = await generateText({ - model: workersai("@cf/zai-org/glm-4.7-flash"), + model: ai("@cf/zai-org/glm-4.7-flash"), prompt: "Use available tools to summarize the latest issue activity.", tools: this.mcp.getAITools(), }); diff --git a/src/content/docs/agents/tools/sandbox.mdx b/src/content/docs/agents/tools/sandbox.mdx index f6c5cecf49d..7e4b17b3af0 100644 --- a/src/content/docs/agents/tools/sandbox.mdx +++ b/src/content/docs/agents/tools/sandbox.mdx @@ -39,7 +39,7 @@ The following [Think](/agents/harnesses/think/) agent gives the model one tool, import { Think } from "@cloudflare/think"; import { routeAgentRequest } from "agents"; import { tool } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { z } from "zod"; const MAX_OUTPUT_CHARS = 10_000; @@ -59,7 +59,7 @@ export class CodeAgent extends Think { } getModel() { - return createWorkersAI({ binding: this.env.AI })( + return createAI({ binding: this.env.AI })( "@cf/moonshotai/kimi-k2.6", ); } diff --git a/src/content/docs/ai-gateway/integrations/pi-ai.mdx b/src/content/docs/ai-gateway/integrations/pi-ai.mdx new file mode 100644 index 00000000000..0a23ea2c473 --- /dev/null +++ b/src/content/docs/ai-gateway/integrations/pi-ai.mdx @@ -0,0 +1,128 @@ +--- +title: Pi AI +description: Call AI Gateway from Pi AI with createAI from agents/models/pi-ai. +pcx_content_type: configuration +sidebar: + order: 2 +products: + - ai-gateway +--- + +import { + PackageManagers, + TypeScriptExample, + WranglerConfig, +} from "~/components"; + +To call AI Gateway from [Pi AI](https://github.com/earendil-works/pi/tree/main/packages/ai), use `createAI` from `agents/models/pi-ai`. It calls models from the [model catalog](/ai/models/), [dynamic routes](/ai-gateway/features/dynamic-routing/), and Pi AI's own model objects through your gateway. This page covers the AI Gateway features. For the full reference, refer to [Pi AI](/agents/models/pi-ai/). + +## Installation + +Install the Agents SDK and Pi AI. `createAI` needs `@earendil-works/pi-ai@~1.3.0`: + + + +In a Worker, add the `AI` binding to your Wrangler configuration: + + + +```jsonc +{ + "ai": { + "binding": "AI", + }, +} +``` + + + +## Call a catalog model + +Pass a `/` from Pi AI's AI Gateway catalog. You do not need a vendor key. AI Gateway bills the request through [Unified Billing](/ai-gateway/features/unified-billing/), or uses a [key stored on the gateway](/ai-gateway/configuration/bring-your-own-keys/): + + + +```ts +import { createAI } from "agents/models/pi-ai"; + +export default { + async fetch(request: Request, env: Env) { + const ai = createAI({ binding: env.AI, id: "my-gateway" }); + + const reply = await ai.complete(ai("anthropic/claude-opus-5.5"), { + messages: [ + { role: "user", content: "Write a short story", timestamp: Date.now() }, + ], + }); + + return Response.json(reply.content); + }, +}; +``` + + + +Use the catalog's spelling of the id, such as `anthropic/claude-opus-5.5` or `openai/gpt-5.5`. The request goes in the model's own format, Anthropic Messages or OpenAI Responses, so Pi AI's own parsers read the answer and thinking works as it does with the vendor. `id` sets the gateway and defaults to `default`. A dynamic route on that gateway is `dynamic/`, for example `ai("dynamic/support")`. + +## Call a Pi AI model object + +Pass any model from a Pi AI registry. The request goes to the vendor's [provider-native endpoint](/ai-gateway/usage/providers/) on your gateway, and AI Gateway supplies the credential: + +```ts +import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all"; +import { createAI } from "agents/models/pi-ai"; + +const ai = createAI({ binding: env.AI, id: "my-gateway" }); + +const model = ai(getBuiltinModel("anthropic", "claude-opus-5-5")); +``` + +A model object keeps the vendor's own id, such as `claude-opus-5-5`, and all of Pi AI's metadata for it. + +## Fall back to another model + +`fallback` lists models to try in order if the primary model fails before it produces any output. Mix catalog ids, dynamic routes, Workers AI ids, and model objects: + +```ts +const model = ai("anthropic/claude-opus-5.5", { + fallback: ["openai/gpt-5.5", "@cf/zai-org/glm-4.7-flash"], +}); +``` + +## Set gateway options + +Set [caching](/ai-gateway/features/caching/), logging, metadata, timeouts, and retries on `createAI`, on a model, or per call: + +```ts +const ai = createAI({ + binding: env.AI, + id: "my-gateway", + cacheTtl: 60, + metadata: { app: "support-bot" }, +}); + +const model = ai("openai/gpt-5.5", { byokAlias: "production" }); +``` + +For every option, refer to [Cache, tag, and time out requests](/agents/models/pi-ai/#cache-tag-and-time-out-requests). + +## Use an account id and API token + +To use a gateway in another account, or to call from outside a Worker, pass `accountId` and `apiToken` instead of `binding`: + +```ts +const ai = createAI({ + accountId: "", + apiToken: env.CLOUDFLARE_API_TOKEN, + gateway: "my-gateway", +}); +``` + +[Create an API token](/fundamentals/api/get-started/create-token/) with the **Workers AI Read** permission for model ids and **AI Gateway Run** for model objects. + +## Related resources + +- [Pi AI provider reference](/agents/models/pi-ai/) +- [Pi harness](/agents/harnesses/pi/) +- [Model catalog](/ai/models/) +- [Vercel AI SDK](/ai-gateway/integrations/vercel-ai-sdk/) diff --git a/src/content/docs/ai-gateway/integrations/vercel-ai-sdk.mdx b/src/content/docs/ai-gateway/integrations/vercel-ai-sdk.mdx index 41463ad0e53..2bbf8f94357 100644 --- a/src/content/docs/ai-gateway/integrations/vercel-ai-sdk.mdx +++ b/src/content/docs/ai-gateway/integrations/vercel-ai-sdk.mdx @@ -1,6 +1,6 @@ --- title: Vercel AI SDK -description: Route Vercel AI SDK requests through AI Gateway using the ai-gateway-provider package. +description: Call AI Gateway from the AI SDK with createAI from agents/models/ai-sdk. pcx_content_type: configuration sidebar: order: 2 @@ -8,60 +8,130 @@ products: - ai-gateway --- -import { Details, Tabs, TabItem, TypeScriptExample } from "~/components"; -import CodeSnippets from "~/components/ai-gateway/code-examples.astro"; +import { + PackageManagers, + TypeScriptExample, + WranglerConfig, +} from "~/components"; -The [Vercel AI SDK](https://sdk.vercel.ai/) is a TypeScript library for building AI applications. The SDK supports many different AI providers, tools for streaming completions, and more. -To use Cloudflare AI Gateway with Vercel AI SDK, you will need to use the `ai-gateway-provider` package. +To call AI Gateway from the [AI SDK](https://ai-sdk.dev/), use `createAI` from `agents/models/ai-sdk`. It calls models from the [model catalog](/ai/models/), [dynamic routes](/ai-gateway/features/dynamic-routing/), and a vendor's own AI SDK models through your gateway. This page covers the AI Gateway features. For the full reference, refer to [AI SDK](/agents/models/ai-sdk/). + +The `ai-gateway-provider` package is deprecated. Use `createAI` instead. ## Installation -```bash -npm install ai-gateway-provider -``` +Install the Agents SDK and the AI SDK. `createAI` needs `ai@^7` and `@ai-sdk/provider@^4`: + + -## Examples +In a Worker, add the `AI` binding to your Wrangler configuration: - + -### AI binding with third-party models +```jsonc +{ + "ai": { + "binding": "AI", + }, +} +``` -If you are already using the [`workers-ai-provider`](https://www.npmjs.com/package/workers-ai-provider) package, you can route requests through AI Gateway to call third-party models without needing separate provider SDKs. Pass a `gateway` option with your gateway ID to `createWorkersAI`: + + +## Call a catalog model + +Pass an `/` id from the [model catalog](/ai/models/). You do not need a vendor package or a vendor key. AI Gateway bills the request through [Unified Billing](/ai-gateway/features/unified-billing/), or uses a [key stored on the gateway](/ai-gateway/configuration/bring-your-own-keys/): ```ts -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { streamText } from "ai"; export default { - async fetch(request, env) { - const workersai = createWorkersAI({ - binding: env.AI, - gateway: { id: "my-gateway" }, - }); + async fetch(request: Request, env: Env) { + const ai = createAI({ binding: env.AI, id: "my-gateway" }); const result = streamText({ - model: workersai("openai/gpt-4o"), + model: ai("anthropic/claude-opus-5.5"), messages: [{ role: "user", content: "Write a short story" }], }); return result.toTextStreamResponse(); }, -} satisfies ExportedHandler; +}; ``` -This works with any [supported provider and model](/ai/models/) available through AI Gateway. +Use the catalog's spelling of the id, such as `anthropic/claude-opus-5.5` or `openai/gpt-5.5`. `id` sets the gateway and defaults to `default`. A [dynamic route](/ai-gateway/features/dynamic-routing/) on that gateway is `dynamic/`, for example `ai("dynamic/support")`. + +## Call a vendor's model object + +Pass a model from the vendor's own AI SDK package. The request goes unchanged to the vendor's [provider-native endpoint](/ai-gateway/usage/providers/) on your gateway. AI Gateway holds the credential, so the vendor's `apiKey` is a placeholder: + +```ts +import { createAnthropic } from "@ai-sdk/anthropic"; +import { createAI } from "agents/models/ai-sdk"; + +const ai = createAI({ binding: env.AI, id: "my-gateway" }); +const anthropic = createAnthropic({ apiKey: "cloudflare" }); + +const model = ai(anthropic("claude-opus-5-5")); +``` + +Use the vendor's own model id here, such as `claude-opus-5-5`. To send a key you hold instead, refer to [Unified billing and BYOK](/agents/models/ai-sdk/#unified-billing-and-byok). + +## Fall back to another model + +`fallback` lists models to try in order if the primary model fails before it produces any output. Mix catalog ids, dynamic routes, Workers AI ids, and model objects: -### Fallback Providers +```ts +const model = ai(anthropic("claude-opus-5-5"), { + fallback: ["openai/gpt-5.5", "@cf/zai-org/glm-4.7-flash"], +}); +``` + +## Set gateway options + +Set [caching](/ai-gateway/features/caching/), logging, metadata, timeouts, and retries on the provider, on a model, or per call under `providerOptions.cloudflare`: + +```ts +const ai = createAI({ + binding: env.AI, + id: "my-gateway", + cacheTtl: 60, + metadata: { app: "support-bot" }, +}); + +await generateText({ + model: ai("openai/gpt-5.5"), + prompt: "Summarize this ticket", + providerOptions: { + cloudflare: { skipCache: true, metadata: { userId: "u_123" } }, + }, +}); +``` + +For every option, refer to [Set options](/agents/models/ai-sdk/#set-options). + +## Use an account id and API token -To specify model or provider fallbacks to handle request failures and ensure reliability, you can pass an array of models to the `model` option. +To use a gateway in another account, or to call from outside a Worker, pass `accountId` and `apiToken` instead of `binding`: -```js title="" -const { text } = await generateText({ - model: aigateway([openai.chat("gpt-5.1"), anthropic("claude-sonnet-4-5")]), - prompt: "Write a vegetarian lasagna recipe for 4 people.", +```ts +const ai = createAI({ + accountId: "", + apiToken: env.CLOUDFLARE_API_TOKEN, + gateway: "my-gateway", }); ``` + +The call forms and options are the same. [Create an API token](/fundamentals/api/get-started/create-token/) with the **Workers AI Read** permission for model ids and **AI Gateway Run** for model objects. + +## Related resources + +- [AI SDK provider reference](/agents/models/ai-sdk/) +- [Model catalog](/ai/models/) +- [Dynamic routing](/ai-gateway/features/dynamic-routing/) +- [Unified Billing](/ai-gateway/features/unified-billing/) diff --git a/src/content/docs/ai-search/agent-sdks/agents-sdk.mdx b/src/content/docs/ai-search/agent-sdks/agents-sdk.mdx index 4aa95cb2528..1a4c84fd5df 100644 --- a/src/content/docs/ai-search/agent-sdks/agents-sdk.mdx +++ b/src/content/docs/ai-search/agent-sdks/agents-sdk.mdx @@ -53,9 +53,9 @@ cd ai-search-agent ## 2. Install the Agents SDK packages -Install the Agents SDK, the AI SDK, and the Workers AI provider: +Install the Agents SDK and the AI SDK. The example calls Workers AI with [`createAI`](/agents/models/ai-sdk/) from `agents/models/ai-sdk`: - + ## 3. Bind your Worker to AI Search @@ -100,7 +100,7 @@ Create `src/server.ts`. New AI Search instances use [hybrid search](/ai-search/c ```ts import { AIChatAgent } from "@cloudflare/ai-chat"; import { routeAgentRequest } from "agents"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { streamText, convertToModelMessages, tool, stepCountIs } from "ai"; import { z } from "zod"; @@ -149,10 +149,10 @@ export class SearchAgent extends AIChatAgent { // The agent exposes AI Search's search() as a tool instead of using AI // Search's own chat model, so this model must support function calling to // decide when to search and drive the tool calls. - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = streamText({ - model: workersai("@cf/zai-org/glm-5.2"), + model: ai("@cf/zai-org/glm-5.2"), system: "You are a support assistant. Use search_knowledge_base to find " + "relevant content before answering, and cite what you use.", diff --git a/src/content/docs/ai-search/how-to/talk-to-your-knowledge-base.mdx b/src/content/docs/ai-search/how-to/talk-to-your-knowledge-base.mdx index 6500558616f..2059cb136fd 100644 --- a/src/content/docs/ai-search/how-to/talk-to-your-knowledge-base.mdx +++ b/src/content/docs/ai-search/how-to/talk-to-your-knowledge-base.mdx @@ -123,7 +123,7 @@ import { type VoiceTurnContext, } from "@cloudflare/voice"; import { generateText, tool, stepCountIs } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { z } from "zod"; const VoiceAgent = withVoice(Agent); @@ -137,11 +137,11 @@ export class TalkToDocs extends VoiceAgent { // the reply, calling AI Search as a retrieval tool when it needs facts from // your knowledge base. async onTurn(transcript: string, context: VoiceTurnContext) { - const workersai = createWorkersAI({ binding: this.env.AI }); + const ai = createAI({ binding: this.env.AI }); const result = await generateText({ // Use a Workers AI model that supports function calling. - model: workersai("@cf/zai-org/glm-5.2"), + model: ai("@cf/zai-org/glm-5.2"), system: "You are a helpful voice assistant that answers from a Cloudflare AI Search knowledge base. " + "For questions about the product, call the searchKnowledgeBase tool first and answer using the results. " + diff --git a/src/content/docs/sandbox/get-started/build-an-ai-code-interpreter.mdx b/src/content/docs/sandbox/get-started/build-an-ai-code-interpreter.mdx index e62d604eb3a..2688cc57b1d 100644 --- a/src/content/docs/sandbox/get-started/build-an-ai-code-interpreter.mdx +++ b/src/content/docs/sandbox/get-started/build-an-ai-code-interpreter.mdx @@ -65,9 +65,9 @@ You will learn how to: cd sandbox-code-interpreter ``` -3. Install the [AI SDK](https://ai-sdk.dev/), the [Workers AI provider](/workers-ai/configuration/ai-sdk/), and [Zod](https://zod.dev/): +3. Install the [Agents SDK](/agents/), the [AI SDK](https://ai-sdk.dev/), and [Zod](https://zod.dev/). The Worker calls Workers AI with [`createAI`](/agents/models/ai-sdk/) from `agents/models/ai-sdk`: - + 4. Replace `wrangler.jsonc` to add a Workers AI binding and a Worker Loader binding: @@ -192,7 +192,7 @@ Replace `src/index.ts`. The Worker gives the model a `runJavaScript` tool and re ```ts import { generateText, isStepCount, tool } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; +import { createAI } from "agents/models/ai-sdk"; import { z } from "zod"; import { runJavaScript, type Output } from "./sandbox"; @@ -223,12 +223,12 @@ export default { ); } - const workersai = createWorkersAI({ binding: env.AI }); + const ai = createAI({ binding: env.AI }); const runs: Run[] = []; try { const result = await generateText({ - model: workersai(model), + model: ai(model), instructions, prompt: body.data.question, tools: { diff --git a/src/content/docs/sandbox/sdk/tutorials/ai-code-executor.mdx b/src/content/docs/sandbox/sdk/tutorials/ai-code-executor.mdx index c37b20bc743..21edb1077b0 100644 --- a/src/content/docs/sandbox/sdk/tutorials/ai-code-executor.mdx +++ b/src/content/docs/sandbox/sdk/tutorials/ai-code-executor.mdx @@ -250,4 +250,4 @@ You created an AI code execution system that: - [Anthropic Claude documentation](https://docs.anthropic.com/) - [Workers AI](/workers-ai/) - Use Cloudflare's built-in models -- [workers-ai-provider package](https://github.com/cloudflare/ai/tree/main/packages/workers-ai-provider) - Official Workers AI integration +- [AI SDK provider](/agents/models/ai-sdk/) - Call Workers AI and AI Gateway models from the AI SDK diff --git a/src/content/docs/workers-ai/configuration/ai-sdk.mdx b/src/content/docs/workers-ai/configuration/ai-sdk.mdx index b801f722eff..9460c961e5c 100644 --- a/src/content/docs/workers-ai/configuration/ai-sdk.mdx +++ b/src/content/docs/workers-ai/configuration/ai-sdk.mdx @@ -1,129 +1,194 @@ --- pcx_content_type: configuration title: Vercel AI SDK -description: Use Workers AI with the Vercel AI SDK for streaming text generation, tool calls, and structured output. +description: Use Workers AI models with the AI SDK through createAI from agents/models/ai-sdk. sidebar: order: 3 products: - workers-ai --- -import { PackageManagers } from "~/components"; +import { + PackageManagers, + TypeScriptExample, + WranglerConfig, +} from "~/components"; -Workers AI can be used with the [Vercel AI SDK](https://sdk.vercel.ai/) for JavaScript and TypeScript codebases. +To use Workers AI with the [AI SDK](https://ai-sdk.dev/), use `createAI` from `agents/models/ai-sdk`. This page covers Workers AI models. For AI Gateway catalog models, fallback, gateway options, and the full option reference, refer to [AI SDK](/agents/models/ai-sdk/). + +The `workers-ai-provider` package is deprecated. Use `createAI` instead. ## Setup -Install the [`workers-ai-provider` provider](https://sdk.vercel.ai/providers/community-providers/cloudflare-workers-ai): +Install the Agents SDK and the AI SDK. `createAI` needs `ai@^7` and `@ai-sdk/provider@^4`: + + - +Add the `AI` binding to your Wrangler configuration: -Then, add an AI binding in your Workers project Wrangler file: + -```toml -[ai] -binding = "AI" +```jsonc +{ + "ai": { + "binding": "AI", + }, +} ``` -## Models + -The AI SDK can be configured to work with [any AI model](/workers-ai/models/). +## Choose a model -```js -import { createWorkersAI } from "workers-ai-provider"; +Pass the id of any [Workers AI model](/workers-ai/models/). Model ids start with `@cf/` and autocomplete from `@cloudflare/workers-types`: -const workersai = createWorkersAI({ binding: env.AI }); +```ts +import { createAI } from "agents/models/ai-sdk"; -// Choose any model: https://developers.cloudflare.com/workers-ai/models/ -const model = workersai("@cf/meta/llama-3.1-8b-instruct", {}); -``` +const ai = createAI({ binding: env.AI }); -## Generate Text +const model = ai("@cf/zai-org/glm-4.7-flash"); +``` -Once you have selected your model, you can generate text from a given prompt. +## Generate text -```js -import { createWorkersAI } from 'workers-ai-provider'; -import { generateText } from 'ai'; + -type Env = { - AI: Ai; -}; +```ts +import { createAI } from "agents/models/ai-sdk"; +import { generateText } from "ai"; export default { - async fetch(_: Request, env: Env) { - const workersai = createWorkersAI({ binding: env.AI }); - const result = await generateText({ - model: workersai('@cf/meta/llama-2-7b-chat-int8'), - prompt: 'Write a 50-word essay about hello world.', - }); - - return new Response(result.text); - }, + async fetch(request: Request, env: Env) { + const ai = createAI({ binding: env.AI }); + + const { text } = await generateText({ + model: ai("@cf/zai-org/glm-4.7-flash"), + prompt: "Write a 50-word essay about hello world.", + }); + + return new Response(text); + }, }; ``` -## Stream Text + -For longer responses, consider streaming responses to provide as the generation completes. +## Stream text -```js -import { createWorkersAI } from 'workers-ai-provider'; -import { streamText } from 'ai'; +For longer responses, stream the output as the model generates it: -type Env = { - AI: Ai; -}; + + +```ts +import { createAI } from "agents/models/ai-sdk"; +import { streamText } from "ai"; export default { - async fetch(_: Request, env: Env) { - const workersai = createWorkersAI({ binding: env.AI }); - const result = streamText({ - model: workersai('@cf/meta/llama-2-7b-chat-int8'), - prompt: 'Write a 50-word essay about hello world.', - }); - - return result.toTextStreamResponse({ - headers: { - // add these headers to ensure that the - // response is chunked and streamed - 'Content-Type': 'text/x-unknown', - 'content-encoding': 'identity', - 'transfer-encoding': 'chunked', - }, - }); - }, + async fetch(request: Request, env: Env) { + const ai = createAI({ binding: env.AI }); + + const result = streamText({ + model: ai("@cf/zai-org/glm-4.7-flash"), + prompt: "Write a 50-word essay about hello world.", + }); + + return result.toTextStreamResponse(); + }, }; ``` -## Generate Structured Objects + -You can provide a Zod schema to generate a structured JSON response. +## Generate structured output -```js -import { createWorkersAI } from 'workers-ai-provider'; -import { generateObject } from 'ai'; -import { z } from 'zod'; +Pass a Zod schema to get a typed JSON response: -type Env = { - AI: Ai; -}; + + +```ts +import { createAI } from "agents/models/ai-sdk"; +import { generateText, Output } from "ai"; +import { z } from "zod"; export default { - async fetch(_: Request, env: Env) { - const workersai = createWorkersAI({ binding: env.AI }); - const result = await generateObject({ - model: workersai('@cf/meta/llama-3.1-8b-instruct'), - prompt: 'Generate a Lasagna recipe', - schema: z.object({ - recipe: z.object({ - ingredients: z.array(z.string()), - description: z.string(), - }), - }), - }); - - return Response.json(result.object); - }, + async fetch(request: Request, env: Env) { + const ai = createAI({ binding: env.AI }); + + const { output } = await generateText({ + model: ai("@cf/zai-org/glm-4.7-flash"), + prompt: "Generate a lasagna recipe.", + output: Output.object({ + schema: z.object({ + recipe: z.object({ + ingredients: z.array(z.string()), + description: z.string(), + }), + }), + }), + }); + + return Response.json(output); + }, }; ``` + + + +## Use other model types + +Embeddings, images, speech, transcription, and reranking come from the same provider: + +```ts +import { + embedMany, + generateImage, + generateSpeech, + rerank, + transcribe, +} from "ai"; + +const { embeddings } = await embedMany({ + model: ai.embedding("@cf/baai/bge-base-en-v1.5"), + values: ["hello", "world"], +}); + +const { image } = await generateImage({ + model: ai.image("@cf/black-forest-labs/flux-1-schnell"), + prompt: "A cyberpunk lizard", +}); + +const { audio } = await generateSpeech({ + model: ai.speech("@cf/deepgram/aura-1"), + text: "Hello from Cloudflare.", +}); + +const { text } = await transcribe({ + model: ai.transcription("@cf/openai/whisper-large-v3-turbo"), + audio: audio.uint8Array, +}); + +const { rerankedDocuments } = await rerank({ + model: ai.reranking("@cf/baai/bge-reranker-base"), + documents: ["a cyberpunk lizard", "a rainy Tuesday"], + query: "Which one is cooler?", +}); +``` + +For model-specific settings, refer to [Use other modalities](/agents/models/ai-sdk/#use-other-modalities). + +## Use AI Gateway + +Requests go through [AI Gateway](/ai-gateway/). The gateway defaults to `default`. To use another gateway, set its `id`: + +```ts +const ai = createAI({ binding: env.AI, id: "my-gateway" }); +``` + +The same provider also calls models from the [AI Gateway catalog](/ai/models/), such as `anthropic/claude-opus-5.5`. Refer to [AI SDK](/agents/models/ai-sdk/) for catalog models, caching, fallback, and the other gateway options. + +## Related resources + +- [AI SDK provider reference](/agents/models/ai-sdk/) +- [Workers AI models](/workers-ai/models/) +- [AI SDK documentation](https://ai-sdk.dev/docs/introduction) diff --git a/src/content/docs/workers-ai/configuration/pi-ai.mdx b/src/content/docs/workers-ai/configuration/pi-ai.mdx new file mode 100644 index 00000000000..9fa672c0d7c --- /dev/null +++ b/src/content/docs/workers-ai/configuration/pi-ai.mdx @@ -0,0 +1,123 @@ +--- +title: Pi AI +description: Use Workers AI models with Pi AI through createAI from agents/models/pi-ai. +pcx_content_type: configuration +sidebar: + order: 3 +products: + - workers-ai +--- + +import { + PackageManagers, + TypeScriptExample, + WranglerConfig, +} from "~/components"; + +To use Workers AI with [Pi AI](https://github.com/earendil-works/pi/tree/main/packages/ai), use `createAI` from `agents/models/pi-ai`. It returns Pi AI `Model` objects, so Workers AI models work with Pi AI's `stream` and `complete`, with [Pi Durable](https://github.com/earendil-works/pi/tree/main/packages/durable), and with the [Pi harness](/agents/harnesses/pi/). This page covers Workers AI models. For AI Gateway catalog models, fallback, gateway options, and the full reference, refer to [Pi AI](/agents/models/pi-ai/). + +## Setup + +Install the Agents SDK and Pi AI. `createAI` needs `@earendil-works/pi-ai@~1.3.0`: + + + +Add the `AI` binding to your Wrangler configuration: + + + +```jsonc +{ + "ai": { + "binding": "AI", + }, +} +``` + + + +## Generate text + +Name a Workers AI model by its `@cf/` id. The provider carries Pi AI's entry points, so nothing is registered globally: + + + +```ts +import { createAI } from "agents/models/pi-ai"; + +export default { + async fetch(request: Request, env: Env) { + const ai = createAI({ binding: env.AI }); + + const reply = await ai.complete(ai("@cf/zai-org/glm-4.7-flash"), { + messages: [ + { role: "user", content: "What is Cloudflare?", timestamp: Date.now() }, + ], + }); + + // `content` holds text, thinking, and tool call parts. + const text = reply.content + .filter((part) => part.type === "text") + .map((part) => part.text) + .join(""); + + return Response.json({ text }); + }, +}; +``` + + + +## Stream text + +`ai.stream()` returns Pi AI's event stream. `ai.streamSimple()` and `ai.completeSimple()` take Pi AI's simple options, such as a `reasoning` level and `maxTokens`: + +```ts +const model = ai("@cf/zai-org/glm-4.7-flash"); +const context = { + messages: [{ role: "user", content: "Hi", timestamp: Date.now() }], +}; + +for await (const event of ai.stream(model, context)) { + // text_delta, thinking_delta, toolcall_*, done, error +} +``` + +A compatibility layer turns each Workers AI model's response into the shape Pi AI's parsers read, so tool calls and reasoning work the same across model families. + +## Set model details + +A Workers AI id that Pi AI does not list resolves with a 128,000 token context window, 8,192 output tokens, and zero cost. Pass the real values when you know them: + +```ts +const model = ai("@cf/zai-org/glm-4.7-flash", { + contextWindow: 131_072, + maxTokens: 16_384, +}); +``` + +## Generate images + +```ts +const images = ai.images("@cf/black-forest-labs/flux-1-schnell", { steps: 8 }); +const result = await ai.generateImages(images, { + input: [{ type: "text", text: "A red bicycle" }], +}); +``` + +## Use AI Gateway + +Every request goes through AI Gateway. Set the gateway with `id`, which defaults to `default`: + +```ts +const ai = createAI({ binding: env.AI, id: "my-gateway" }); +``` + +To call from outside a Worker, pass `accountId` and `apiToken` instead of `binding`. For gateway options, catalog models, and fallback, refer to [Pi AI](/agents/models/pi-ai/). + +## Related resources + +- [Pi AI provider reference](/agents/models/pi-ai/) +- [Pi harness](/agents/harnesses/pi/) +- [Workers AI models](/workers-ai/models/) +- [Vercel AI SDK](/workers-ai/configuration/ai-sdk/)