This commit is contained in:
Rohan Mukherjee 2026-01-30 23:53:09 +08:00 committed by GitHub
commit 7d9f0f251a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 283 additions and 1 deletions

View File

@ -0,0 +1,97 @@
---
title: "Cloudflare AI Gateway"
summary: "Cloudflare AI Gateway setup (analytics, caching, rate limiting)"
read_when:
- You want to use Cloudflare AI Gateway with OpenClaw
- You need analytics, caching, or rate limiting for AI requests
---
# Cloudflare AI Gateway
The [Cloudflare AI Gateway](https://developers.cloudflare.com/ai-gateway/) provides visibility and control over your AI applications with features like analytics, logging, caching, rate limiting, request retries, and model fallback.
- Provider: `cloudflare-ai-gateway`
- Auth: Account ID + Gateway ID + API Key (optional for unauthenticated gateway)
- API: Anthropic Messages compatible (via provider-specific endpoints)
## Quick start
1) Set up your Cloudflare AI Gateway credentials:
```bash
openclaw onboard --auth-choice cloudflare-ai-gateway-api-key
```
You'll be prompted to enter:
- Cloudflare Account ID (found in your Cloudflare dashboard)
- Cloudflare AI Gateway ID (the name you give your gateway)
- Cloudflare AI Gateway API key (optional, only needed for authenticated gateways)
2) Set a default model:
```json5
{
agents: {
defaults: {
model: { primary: "cloudflare-ai-gateway/anthropic/claude-sonnet-4-5" }
}
}
}
```
## Non-interactive example
```bash
openclaw onboard --non-interactive \
--mode local \
--auth-choice cloudflare-ai-gateway-api-key \
--cloudflare-ai-gateway-account-id "your-account-id" \
--cloudflare-ai-gateway-gateway-id "your-gateway-id" \
--cloudflare-ai-gateway-api-key "your-api-key"
```
## Setting up your Cloudflare AI Gateway
1. Log into the [Cloudflare dashboard](https://dash.cloudflare.com/)
2. Go to **AI** > **AI Gateway**
3. Select **Create Gateway**
4. Enter your **Gateway name** (this becomes your gateway ID)
5. (Optional) Enable authentication for added security
## Features
### Analytics
View metrics such as the number of requests, tokens, and cost of running your application.
### Caching
Serve requests directly from Cloudflare's cache for faster requests and cost savings.
### Rate Limiting
Control how your application scales by limiting the number of requests.
### Request Retry and Fallback
Improve resilience by defining request retry and model fallbacks in case of an error.
## Supported Providers
Cloudflare AI Gateway works with:
- OpenAI
- Anthropic (Claude)
- Google AI Studio (Gemini)
- Azure OpenAI
- AWS Bedrock
- Workers AI
- And many more
## Environment note
If the Gateway runs as a daemon (launchd/systemd), make sure credentials are available to that process (for example, in `~/.openclaw/.env` or via `env.shellEnv`).
You can also set environment variables:
- `CLOUDFLARE_AI_GATEWAY_API_KEY` - Your Cloudflare AI Gateway API key
- Account ID and Gateway ID are stored in the auth profile metadata
## Learn more
- [Cloudflare AI Gateway Documentation](https://developers.cloudflare.com/ai-gateway/)
- [Getting Started Guide](https://developers.cloudflare.com/ai-gateway/get-started/)
- [Authentication](https://developers.cloudflare.com/ai-gateway/configuration/authentication/)

View File

@ -278,6 +278,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
xai: "XAI_API_KEY", xai: "XAI_API_KEY",
openrouter: "OPENROUTER_API_KEY", openrouter: "OPENROUTER_API_KEY",
"vercel-ai-gateway": "AI_GATEWAY_API_KEY", "vercel-ai-gateway": "AI_GATEWAY_API_KEY",
"cloudflare-ai-gateway": "CLOUDFLARE_AI_GATEWAY_API_KEY",
moonshot: "MOONSHOT_API_KEY", moonshot: "MOONSHOT_API_KEY",
"kimi-code": "KIMICODE_API_KEY", "kimi-code": "KIMICODE_API_KEY",
minimax: "MINIMAX_API_KEY", minimax: "MINIMAX_API_KEY",

View File

@ -52,7 +52,7 @@ export function registerOnboardCommand(program: Command) {
.option("--mode <mode>", "Wizard mode: local|remote") .option("--mode <mode>", "Wizard mode: local|remote")
.option( .option(
"--auth-choice <choice>", "--auth-choice <choice>",
"Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|xiaomi-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", "Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|cloudflare-ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|xiaomi-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip",
) )
.option( .option(
"--token-provider <id>", "--token-provider <id>",
@ -68,6 +68,9 @@ export function registerOnboardCommand(program: Command) {
.option("--openai-api-key <key>", "OpenAI API key") .option("--openai-api-key <key>", "OpenAI API key")
.option("--openrouter-api-key <key>", "OpenRouter API key") .option("--openrouter-api-key <key>", "OpenRouter API key")
.option("--ai-gateway-api-key <key>", "Vercel AI Gateway API key") .option("--ai-gateway-api-key <key>", "Vercel AI Gateway API key")
.option("--cloudflare-ai-gateway-account-id <id>", "Cloudflare Account ID")
.option("--cloudflare-ai-gateway-gateway-id <id>", "Cloudflare AI Gateway ID")
.option("--cloudflare-ai-gateway-api-key <key>", "Cloudflare AI Gateway API key")
.option("--moonshot-api-key <key>", "Moonshot API key") .option("--moonshot-api-key <key>", "Moonshot API key")
.option("--kimi-code-api-key <key>", "Kimi Code API key") .option("--kimi-code-api-key <key>", "Kimi Code API key")
.option("--gemini-api-key <key>", "Gemini API key") .option("--gemini-api-key <key>", "Gemini API key")

View File

@ -96,6 +96,12 @@ const AUTH_CHOICE_GROUP_DEFS: {
hint: "API key", hint: "API key",
choices: ["ai-gateway-api-key"], choices: ["ai-gateway-api-key"],
}, },
{
value: "cloudflare-ai-gateway",
label: "Cloudflare AI Gateway",
hint: "Account ID + Gateway ID + API key",
choices: ["cloudflare-ai-gateway-api-key"],
},
{ {
value: "moonshot", value: "moonshot",
label: "Moonshot AI", label: "Moonshot AI",
@ -146,6 +152,11 @@ export function buildAuthChoiceOptions(params: {
value: "ai-gateway-api-key", value: "ai-gateway-api-key",
label: "Vercel AI Gateway API key", label: "Vercel AI Gateway API key",
}); });
options.push({
value: "cloudflare-ai-gateway-api-key",
label: "Cloudflare AI Gateway",
hint: "Account ID + Gateway ID + API key",
});
options.push({ value: "moonshot-api-key", label: "Moonshot AI API key" }); options.push({ value: "moonshot-api-key", label: "Moonshot AI API key" });
options.push({ value: "kimi-code-api-key", label: "Kimi Code API key" }); options.push({ value: "kimi-code-api-key", label: "Kimi Code API key" });
options.push({ value: "synthetic-api-key", label: "Synthetic API key" }); options.push({ value: "synthetic-api-key", label: "Synthetic API key" });

View File

@ -13,6 +13,8 @@ import {
} from "./google-gemini-model-default.js"; } from "./google-gemini-model-default.js";
import { import {
applyAuthProfileConfig, applyAuthProfileConfig,
applyCloudflareAiGatewayConfig,
applyCloudflareAiGatewayProviderConfig,
applyKimiCodeConfig, applyKimiCodeConfig,
applyKimiCodeProviderConfig, applyKimiCodeProviderConfig,
applyMoonshotConfig, applyMoonshotConfig,
@ -30,6 +32,7 @@ import {
applyXiaomiConfig, applyXiaomiConfig,
applyXiaomiProviderConfig, applyXiaomiProviderConfig,
applyZaiConfig, applyZaiConfig,
CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
KIMI_CODE_MODEL_REF, KIMI_CODE_MODEL_REF,
MOONSHOT_DEFAULT_MODEL_REF, MOONSHOT_DEFAULT_MODEL_REF,
OPENROUTER_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF,
@ -37,6 +40,7 @@ import {
VENICE_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF,
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
XIAOMI_DEFAULT_MODEL_REF, XIAOMI_DEFAULT_MODEL_REF,
setCloudflareAiGatewayConfig,
setGeminiApiKey, setGeminiApiKey,
setKimiCodeApiKey, setKimiCodeApiKey,
setMoonshotApiKey, setMoonshotApiKey,
@ -75,6 +79,8 @@ export async function applyAuthChoiceApiProviders(
authChoice = "openrouter-api-key"; authChoice = "openrouter-api-key";
} else if (params.opts.tokenProvider === "vercel-ai-gateway") { } else if (params.opts.tokenProvider === "vercel-ai-gateway") {
authChoice = "ai-gateway-api-key"; authChoice = "ai-gateway-api-key";
} else if (params.opts.tokenProvider === "cloudflare-ai-gateway") {
authChoice = "cloudflare-ai-gateway-api-key";
} else if (params.opts.tokenProvider === "moonshot") { } else if (params.opts.tokenProvider === "moonshot") {
authChoice = "moonshot-api-key"; authChoice = "moonshot-api-key";
} else if (params.opts.tokenProvider === "kimi-code") { } else if (params.opts.tokenProvider === "kimi-code") {
@ -224,6 +230,97 @@ export async function applyAuthChoiceApiProviders(
return { config: nextConfig, agentModelOverride }; return { config: nextConfig, agentModelOverride };
} }
if (authChoice === "cloudflare-ai-gateway-api-key") {
let hasCredential = false;
if (
!hasCredential &&
params.opts?.cloudflareAiGatewayAccountId &&
params.opts?.cloudflareAiGatewayGatewayId &&
params.opts?.cloudflareAiGatewayApiKey
) {
await setCloudflareAiGatewayConfig(
params.opts.cloudflareAiGatewayAccountId,
params.opts.cloudflareAiGatewayGatewayId,
normalizeApiKeyInput(params.opts.cloudflareAiGatewayApiKey),
params.agentDir,
);
hasCredential = true;
}
const envKey = resolveEnvApiKey("cloudflare-ai-gateway");
if (envKey) {
const useExisting = await params.prompter.confirm({
message: `Use existing CLOUDFLARE_AI_GATEWAY_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
initialValue: true,
});
if (useExisting) {
const accountId = await params.prompter.text({
message: "Enter Cloudflare Account ID",
validate: (val) => (String(val).trim() ? undefined : "Account ID is required"),
});
const gatewayId = await params.prompter.text({
message: "Enter Cloudflare AI Gateway ID",
validate: (val) => (String(val).trim() ? undefined : "Gateway ID is required"),
});
await setCloudflareAiGatewayConfig(
String(accountId).trim(),
String(gatewayId).trim(),
envKey.apiKey,
params.agentDir,
);
hasCredential = true;
}
}
if (!hasCredential) {
await params.prompter.note(
[
"Cloudflare AI Gateway provides analytics, caching, and rate limiting for AI requests.",
"Get your credentials at: https://dash.cloudflare.com/",
].join("\n"),
"Cloudflare AI Gateway",
);
const accountId = await params.prompter.text({
message: "Enter Cloudflare Account ID",
validate: (val) => (String(val).trim() ? undefined : "Account ID is required"),
});
const gatewayId = await params.prompter.text({
message: "Enter Cloudflare AI Gateway ID",
validate: (val) => (String(val).trim() ? undefined : "Gateway ID is required"),
});
const key = await params.prompter.text({
message: "Enter Cloudflare AI Gateway API key (optional for unauthenticated gateway)",
validate: validateApiKeyInput,
});
await setCloudflareAiGatewayConfig(
String(accountId).trim(),
String(gatewayId).trim(),
normalizeApiKeyInput(String(key)),
params.agentDir,
);
}
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "cloudflare-ai-gateway:default",
provider: "cloudflare-ai-gateway",
mode: "api_key",
});
{
const applied = await applyDefaultModelChoice({
config: nextConfig,
setDefaultModel: params.setDefaultModel,
defaultModel: CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
applyDefaultConfig: applyCloudflareAiGatewayConfig,
applyProviderConfig: applyCloudflareAiGatewayProviderConfig,
noteDefault: CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
noteAgentModel,
prompter: params.prompter,
});
nextConfig = applied.config;
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
}
return { config: nextConfig, agentModelOverride };
}
if (authChoice === "moonshot-api-key") { if (authChoice === "moonshot-api-key") {
let hasCredential = false; let hasCredential = false;

View File

@ -12,6 +12,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
"openai-api-key": "openai", "openai-api-key": "openai",
"openrouter-api-key": "openrouter", "openrouter-api-key": "openrouter",
"ai-gateway-api-key": "vercel-ai-gateway", "ai-gateway-api-key": "vercel-ai-gateway",
"cloudflare-ai-gateway-api-key": "cloudflare-ai-gateway",
"moonshot-api-key": "moonshot", "moonshot-api-key": "moonshot",
"kimi-code-api-key": "kimi-code", "kimi-code-api-key": "kimi-code",
"gemini-api-key": "google", "gemini-api-key": "google",

View File

@ -13,6 +13,7 @@ import {
} from "../agents/venice-models.js"; } from "../agents/venice-models.js";
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import { import {
CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
OPENROUTER_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF,
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
XIAOMI_DEFAULT_MODEL_REF, XIAOMI_DEFAULT_MODEL_REF,
@ -117,6 +118,47 @@ export function applyVercelAiGatewayConfig(cfg: OpenClawConfig): OpenClawConfig
}; };
} }
export function applyCloudflareAiGatewayProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF] = {
...models[CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF],
alias: models[CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF]?.alias ?? "Cloudflare AI Gateway",
};
return {
...cfg,
agents: {
...cfg.agents,
defaults: {
...cfg.agents?.defaults,
models,
},
},
};
}
export function applyCloudflareAiGatewayConfig(cfg: OpenClawConfig): OpenClawConfig {
const next = applyCloudflareAiGatewayProviderConfig(cfg);
const existingModel = next.agents?.defaults?.model;
return {
...next,
agents: {
...next.agents,
defaults: {
...next.agents?.defaults,
model: {
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
? {
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
}
: undefined),
primary: CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
},
},
},
};
}
export function applyOpenrouterConfig(cfg: OpenClawConfig): OpenClawConfig { export function applyOpenrouterConfig(cfg: OpenClawConfig): OpenClawConfig {
const next = applyOpenrouterProviderConfig(cfg); const next = applyOpenrouterProviderConfig(cfg);
const existingModel = next.agents?.defaults?.model; const existingModel = next.agents?.defaults?.model;

View File

@ -116,6 +116,7 @@ export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
export const XIAOMI_DEFAULT_MODEL_REF = "xiaomi/mimo-v2-flash"; export const XIAOMI_DEFAULT_MODEL_REF = "xiaomi/mimo-v2-flash";
export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";
export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5";
export const CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF = "cloudflare-ai-gateway/anthropic/claude-sonnet-4-5";
export async function setZaiApiKey(key: string, agentDir?: string) { export async function setZaiApiKey(key: string, agentDir?: string) {
// Write to resolved agent dir so gateway finds credentials on startup. // Write to resolved agent dir so gateway finds credentials on startup.
@ -177,3 +178,24 @@ export async function setOpencodeZenApiKey(key: string, agentDir?: string) {
agentDir: resolveAuthAgentDir(agentDir), agentDir: resolveAuthAgentDir(agentDir),
}); });
} }
export async function setCloudflareAiGatewayConfig(
accountId: string,
gatewayId: string,
apiKey: string,
agentDir?: string,
) {
upsertAuthProfile({
profileId: "cloudflare-ai-gateway:default",
credential: {
type: "api_key",
provider: "cloudflare-ai-gateway",
key: apiKey,
metadata: {
accountId,
gatewayId,
},
},
agentDir: resolveAuthAgentDir(agentDir),
});
}

View File

@ -5,6 +5,8 @@ export {
export { VENICE_DEFAULT_MODEL_ID, VENICE_DEFAULT_MODEL_REF } from "../agents/venice-models.js"; export { VENICE_DEFAULT_MODEL_ID, VENICE_DEFAULT_MODEL_REF } from "../agents/venice-models.js";
export { export {
applyAuthProfileConfig, applyAuthProfileConfig,
applyCloudflareAiGatewayConfig,
applyCloudflareAiGatewayProviderConfig,
applyKimiCodeConfig, applyKimiCodeConfig,
applyKimiCodeProviderConfig, applyKimiCodeProviderConfig,
applyMoonshotConfig, applyMoonshotConfig,
@ -35,8 +37,10 @@ export {
applyOpencodeZenProviderConfig, applyOpencodeZenProviderConfig,
} from "./onboard-auth.config-opencode.js"; } from "./onboard-auth.config-opencode.js";
export { export {
CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
OPENROUTER_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF,
setAnthropicApiKey, setAnthropicApiKey,
setCloudflareAiGatewayConfig,
setGeminiApiKey, setGeminiApiKey,
setKimiCodeApiKey, setKimiCodeApiKey,
setMinimaxApiKey, setMinimaxApiKey,

View File

@ -13,6 +13,7 @@ export type AuthChoice =
| "openai-api-key" | "openai-api-key"
| "openrouter-api-key" | "openrouter-api-key"
| "ai-gateway-api-key" | "ai-gateway-api-key"
| "cloudflare-ai-gateway-api-key"
| "moonshot-api-key" | "moonshot-api-key"
| "kimi-code-api-key" | "kimi-code-api-key"
| "synthetic-api-key" | "synthetic-api-key"
@ -64,6 +65,9 @@ export type OnboardOptions = {
openaiApiKey?: string; openaiApiKey?: string;
openrouterApiKey?: string; openrouterApiKey?: string;
aiGatewayApiKey?: string; aiGatewayApiKey?: string;
cloudflareAiGatewayAccountId?: string;
cloudflareAiGatewayGatewayId?: string;
cloudflareAiGatewayApiKey?: string;
moonshotApiKey?: string; moonshotApiKey?: string;
kimiCodeApiKey?: string; kimiCodeApiKey?: string;
geminiApiKey?: string; geminiApiKey?: string;