Skip to content
Built with

AI ​

Build & Deploy

Call Workers AI models and outside AI providers from your server code with ai from void/ai. Provider requests go through Cloudflare's AI Gateway.

On this pageRun a model from a route · Stream responses to the browser · Use provider keys and stay within usage limits

Local development ​

Requires: login or token

void connect opens a browser to sign in to Cloudflare.

This step connects a direct Cloudflare project to your account and starts development.

sh
void connect --platform cloudflare
npm run dev

Example output: A browser sign-in to Cloudflare, then the local dev server URL.

When the app imports void/ai, the SDK turns on a remote Workers AI binding in development and preview. Requests use your Cloudflare account and allowance. Workers AI has no local simulator. Development and preview share the void connect login. Sign in with void cloudflare login. A CLOUDFLARE_API_TOKEN wins when set. Use one in automated environments.

On a Cloudflare authentication error, run void cloudflare login as a user with access to the account shown by void cloudflare status. Or set CLOUDFLARE_API_TOKEN. wrangler login does not affect development.

For a team platform, run void connect <platform-url> and link your project. Development requests use that platform's account and limits.

Basic usage ​

This step calls a model with ai.run(). Model names and inputs are typed from @cloudflare/workers-types.

ts
import { defineHandler } from 'void';
import { ai } from 'void/ai';

export const POST = defineHandler(async (c) => {
  const { prompt } = await c.req.json();

  const result = await ai.run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
    messages: [{ role: 'user', content: prompt }],
  });
  return c.json(result);
});

AI methods return promises. Await the result, or return the promise from a handler. See Usage Limits for limit errors.

You can use any model available through Cloudflare's AI binding, including Workers AI models such as @cf/meta/llama-3.3-70b-instruct-fp8-fast and Cloudflare Gateway models such as google/gemini-2.5-flash or openai/gpt-4.1-mini. The input object must match the selected Cloudflare model's schema. Models that return binary data, such as generated images, are returned as a Blob from ai.run().

Streaming ​

This step streams a response with ai.stream(). It returns SSE headers you can send straight from a route handler.

ts
import { defineHandler } from 'void';
import { ai } from 'void/ai';

export const POST = defineHandler(async (c) => {
  const { prompt } = await c.req.json();

  return ai.stream('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
    messages: [{ role: 'user', content: prompt }],
  });
});

Streaming responses end with a void:error SSE event if generation is interrupted after the response starts, or void:limit for a usage limit. fetchStream() from void/client throws UsageLimit for limits before or during streaming and StreamError for other terminal errors. Catch the error in your UI to stop its generating state, retain partial output, and offer retry. This also applies to streaming provider responses and ai.run() with stream: true.

Listing models ​

This step lists available models with ai.models().

ts
const models = await ai.models();

// Filter by task
const textModels = await ai.models({ task: 'Text Generation' });

Markdown conversion ​

This step converts documents to Markdown with ai.toMarkdown().

ts
const result = await ai.toMarkdown([{ name: 'document.pdf', blob: pdfBytes }]);

Usage limits ​

Show usage limits

Workers AI usage is measured in neurons. On a direct deploy, usage counts against your Cloudflare account. Provider-native requests also use your provider credentials and billing.

A team platform uses its own Cloudflare account and may apply per-user limits. Ask your administrator about the available allowance.

Usage limits reject the operation's promise with UsageLimit from void/limits. It includes resource, reason, and an optional retryAt timestamp. In SDK route handlers and Pages actions, an uncaught limit produces a structured HTTP 429 with retry information. In queues, scheduled jobs, and other server code, it remains a rejected promise.

Handling a limit is optional. This step uses instanceof UsageLimit for a fallback or a custom message.

ts
import { defineHandler } from 'void';
import { ai } from 'void/ai';
import { UsageLimit } from 'void/limits';

export const POST = defineHandler(async (c) => {
  const { prompt } = await c.req.json();
  try {
    const result = await ai.run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
      messages: [{ role: 'user', content: prompt }],
    });
    return c.json(result);
  } catch (error) {
    if (error instanceof UsageLimit) {
      return error.response({ message: 'Try again shortly. Your prompt has been kept.' });
    }
    throw error;
  }
});

error.response() creates the structured HTTP 429 and accepts an optional custom message. Other failures reject normally. When using fetchStream(), the same instanceof UsageLimit check gives your client access to the message and retry information.

Cloudflare Gateway models ​

ai.run() mirrors Cloudflare's env.AI.run() model naming and input schemas. Third-party models use Cloudflare model IDs and Cloudflare-managed credentials.

ts
const result = await ai.run('google/gemini-2.5-flash', {
  contents: [
    {
      role: 'user',
      parts: [{ text: 'Explain Durable Objects in one paragraph.' }],
    },
  ],
});

Pass AI Gateway options as the third argument: ai.run(model, inputs, { gateway: { skipCache: true } }). On a team platform, the platform picks the gateway and sets metadata and logging. Options such as skipCache and cacheTtl still apply.

For provider-native requests on a direct deployment, configure ai.gateway.

Provider-native requests ​

Use ai.provider(provider).fetch(path, init) for a provider-native API with your own key. The request goes through the configured AI Gateway and keeps the provider's HTTP shape. On a team platform, it also passes the installation's proxy. Cache, timeout and retry headers such as cf-aig-cache-ttl and cf-aig-max-attempts still apply. The platform ignores logging, metadata and cost headers.

OpenAI ​

ts
import { defineHandler } from 'void';
import { ai } from 'void/ai';

export const POST = defineHandler(async (c) => {
  const { prompt } = await c.req.json();

  return ai.provider('openai').fetch('/chat/completions', {
    body: {
      model: 'gpt-4o',
      messages: [{ role: 'user', content: prompt }],
      max_tokens: 512,
    },
  });
});

Google AI Studio ​

ts
const response = await ai
  .provider('google-ai-studio')
  .fetch('/v1/models/gemini-2.5-flash:generateContent', {
    body: {
      contents: [
        {
          role: 'user',
          parts: [{ text: 'What is Cloudflare?' }],
        },
      ],
    },
  });

Custom providers ​

This step adds a provider outside the default key map. Pass the secret name and API-key header.

ts
const response = await ai
  .provider('custom-provider', {
    apiKeyEnv: 'CUSTOM_PROVIDER_API_KEY',
    apiKeyHeader: 'x-api-key',
    apiKeyPrefix: '',
  })
  .fetch('/v1/respond', {
    body: { prompt: 'Hello' },
  });

Image generation ​

This step generates an image with a Cloudflare-native model, using ai.run() or ai.image().

ts
export const POST = defineHandler(async (c) => {
  const { prompt } = await c.req.json();
  return ai.image('@cf/black-forest-labs/flux-1-schnell', { prompt });
});

Use ai.provider().fetch() for provider-native image APIs. Match the provider's request schema:

ts
export const POST = defineHandler(async (c) => {
  const { prompt } = await c.req.json();

  return ai.provider('openai').fetch('/images/generations', {
    body: {
      model: 'gpt-image-2.5-sunburst',
      prompt,
      size: '1024x1024',
    },
  });
});

For multipart APIs, pass FormData:

ts
export const POST = defineHandler(async (c) => {
  const body = await c.req.parseBody();
  const form = new FormData();
  form.set('model', 'gpt-image-2.5-sunburst');
  form.set('prompt', String(body.prompt));
  form.set('image', body.image as Blob, 'source.png');

  return ai.provider('openai').fetch('/images/edits', { body: form });
});

Provider key convention ​

Provider-native requests use a project secret, such as OPENAI_API_KEY for OpenAI or GOOGLE_API_KEY for Google AI Studio.

See the provider key reference for supported providers and their secret names.

OpenAI-style providers use Authorization: Bearer <key>. Google AI Studio uses x-goog-api-key. Use apiKeyHeader and apiKeyPrefix for custom providers.

This step adds your API key as a project secret for production.

bash
void secret put OPENAI_API_KEY=sk-...

Example output: A confirmation that the secret is stored. The value is not printed.

For local development, add it to .env in the project root:

OPENAI_API_KEY=sk-...

Streaming with provider-native APIs ​

Return the provider response directly. For example, add stream: true to the OpenAI request body shown above.

Next steps ​

implemented.stream AI