feat(ai): add Cloudflare Workers AI as a provider (#3851)

* feat(ai): add Cloudflare Workers AI as a provider

Cloudflare Workers AI hosts open-weight LLMs (Kimi K2.6, GPT-OSS,
GLM-4.7, Llama 4, Gemma 4, Nemotron 3) on Cloudflare's GPU network with
an OpenAI-compatible endpoint. Reuses the openai-completions API
protocol; the per-account URL contains a {CLOUDFLARE_ACCOUNT_ID}
placeholder resolved at request time by a small helper.

Pi automatically sets x-session-affinity for prefix caching:
https://developers.cloudflare.com/workers-ai/features/prompt-caching/

Auth: CLOUDFLARE_API_KEY (matches pi's *_API_KEY convention) +
CLOUDFLARE_ACCOUNT_ID. The User-Agent identifies traffic as
'pi-coding-agent' in Cloudflare analytics.

Verified end-to-end against a real Cloudflare account: 17 e2e tests
pass across stream/empty/tokens/unicode/tool-call-without-result/
total-tokens against @cf/moonshotai/kimi-k2.6.

Cloudflare AI Gateway is a separate, larger change (it requires routing
through provider-specific subpaths with the matching API protocol per
upstream) and will land in a follow-up PR.

* refactor(ai): move Cloudflare User-Agent and session-affinity flag to per-model metadata

Instead of conditionally setting them in openai-completions.ts based on
provider detection, declare them as model-level fields in the catalog
(headers + compat). This is consistent with how the github-copilot and
kimi-coding entries already declare their static headers.

  packages/ai/scripts/generate-models.ts: emit headers and compat fields
  on each cloudflare-workers-ai entry (CLOUDFLARE_STATIC_HEADERS).
  packages/ai/src/providers/openai-completions.ts: drop the
  isCloudflareProvider conditional that injected User-Agent and the
  isCloudflareWorkersAI override of sendSessionAffinityHeaders.
  packages/ai/src/models.generated.ts: re-spliced 8 cloudflare-workers-ai
  entries with headers + compat.

Behavior is unchanged - verified via fetch interceptor that User-Agent
and x-session-affinity / session_id / x-client-request-id are still sent
on outbound requests. 5/5 e2e tests pass.
This commit is contained in:
MC
2026-04-27 17:41:54 -04:00
committed by GitHub
parent 31b48dec23
commit d6e08b3da0
19 changed files with 367 additions and 4 deletions

View File

@@ -57,6 +57,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
- **Mistral**
- **Groq**
- **Cerebras**
- **Cloudflare Workers AI**
- **xAI**
- **OpenRouter**
- **Vercel AI Gateway**
@@ -844,7 +845,7 @@ const ollamaReasoningModel: Model<'openai-completions'> = {
### OpenAI Compatibility Settings
The `openai-completions` API is implemented by many providers with minor differences. By default, the library auto-detects compatibility settings based on `baseUrl` for a small set of known OpenAI-compatible providers (Cerebras, xAI, Chutes, DeepSeek, zAi, OpenCode, etc.). For custom proxies or unknown endpoints, you can override these settings via the `compat` field. For `openai-responses` models, the compat field only supports Responses-specific flags.
The `openai-completions` API is implemented by many providers with minor differences. By default, the library auto-detects compatibility settings based on `baseUrl` for a small set of known OpenAI-compatible providers (Cerebras, xAI, Chutes, DeepSeek, zAi, OpenCode, Cloudflare Workers AI, etc.). For custom proxies or unknown endpoints, you can override these settings via the `compat` field. For `openai-responses` models, the compat field only supports Responses-specific flags.
```typescript
interface OpenAICompletionsCompat {
@@ -1028,6 +1029,7 @@ In Node.js environments, you can set environment variables to avoid passing API
| Mistral | `MISTRAL_API_KEY` |
| Groq | `GROQ_API_KEY` |
| Cerebras | `CEREBRAS_API_KEY` |
| Cloudflare Workers AI | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` |
| xAI | `XAI_API_KEY` |
| Fireworks | `FIREWORKS_API_KEY` |
| OpenRouter | `OPENROUTER_API_KEY` |

View File

@@ -3,6 +3,7 @@
import { writeFileSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { CLOUDFLARE_WORKERS_AI_BASE_URL } from "../src/providers/cloudflare.js";
import {
Api,
type AnthropicMessagesCompat,
@@ -63,6 +64,10 @@ const KIMI_STATIC_HEADERS = {
"User-Agent": "KimiCLI/1.5",
} as const;
const CLOUDFLARE_STATIC_HEADERS = {
"User-Agent": "pi-coding-agent",
} as const;
const AI_GATEWAY_MODELS_URL = "https://ai-gateway.vercel.sh/v1";
const AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
const ZAI_TOOL_STREAM_UNSUPPORTED_MODELS = new Set(["glm-4.5", "glm-4.5-air", "glm-4.5-flash", "glm-4.5v"]);
@@ -376,6 +381,34 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
}
}
// Process Cloudflare Workers AI models
if (data["cloudflare-workers-ai"]?.models) {
for (const [modelId, model] of Object.entries(data["cloudflare-workers-ai"].models)) {
const m = model as ModelsDevModel;
if (m.tool_call !== true) continue;
models.push({
id: modelId,
name: m.name || modelId,
api: "openai-completions",
provider: "cloudflare-workers-ai",
baseUrl: CLOUDFLARE_WORKERS_AI_BASE_URL,
reasoning: m.reasoning === true,
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
cost: {
input: m.cost?.input || 0,
output: m.cost?.output || 0,
cacheRead: m.cost?.cache_read || 0,
cacheWrite: m.cost?.cache_write || 0,
},
contextWindow: m.limit?.context || 4096,
maxTokens: m.limit?.output || 4096,
headers: { ...CLOUDFLARE_STATIC_HEADERS },
compat: { sendSessionAffinityHeaders: true },
});
}
}
// Process xAi models
if (data.xai?.models) {
for (const [modelId, model] of Object.entries(data.xai.models)) {

View File

@@ -118,6 +118,7 @@ function getApiKeyEnvVars(provider: string): readonly string[] | undefined {
opencode: "OPENCODE_API_KEY",
"opencode-go": "OPENCODE_API_KEY",
"kimi-coding": "KIMI_API_KEY",
"cloudflare-workers-ai": "CLOUDFLARE_API_KEY",
};
const envVar = envMap[provider];

View File

@@ -15374,4 +15374,158 @@ export const MODELS = {
maxTokens: 131072,
} satisfies Model<"openai-completions">,
},
"cloudflare-workers-ai": {
"@cf/google/gemma-4-26b-a4b-it": {
id: "@cf/google/gemma-4-26b-a4b-it",
name: "Gemma 4 26B A4B IT",
api: "openai-completions",
provider: "cloudflare-workers-ai",
baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
headers: {"User-Agent":"pi-coding-agent"},
compat: {"sendSessionAffinityHeaders":true},
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.1,
output: 0.3,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 256000,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"@cf/meta/llama-4-scout-17b-16e-instruct": {
id: "@cf/meta/llama-4-scout-17b-16e-instruct",
name: "Llama 4 Scout 17B 16E Instruct",
api: "openai-completions",
provider: "cloudflare-workers-ai",
baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
headers: {"User-Agent":"pi-coding-agent"},
compat: {"sendSessionAffinityHeaders":true},
reasoning: false,
input: ["text", "image"],
cost: {
input: 0.27,
output: 0.85,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 128000,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"@cf/moonshotai/kimi-k2.5": {
id: "@cf/moonshotai/kimi-k2.5",
name: "Kimi K2.5",
api: "openai-completions",
provider: "cloudflare-workers-ai",
baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
headers: {"User-Agent":"pi-coding-agent"},
compat: {"sendSessionAffinityHeaders":true},
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.6,
output: 3,
cacheRead: 0.1,
cacheWrite: 0,
},
contextWindow: 256000,
maxTokens: 256000,
} satisfies Model<"openai-completions">,
"@cf/moonshotai/kimi-k2.6": {
id: "@cf/moonshotai/kimi-k2.6",
name: "Kimi K2.6",
api: "openai-completions",
provider: "cloudflare-workers-ai",
baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
headers: {"User-Agent":"pi-coding-agent"},
compat: {"sendSessionAffinityHeaders":true},
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.95,
output: 4,
cacheRead: 0.16,
cacheWrite: 0,
},
contextWindow: 256000,
maxTokens: 256000,
} satisfies Model<"openai-completions">,
"@cf/nvidia/nemotron-3-120b-a12b": {
id: "@cf/nvidia/nemotron-3-120b-a12b",
name: "Nemotron 3 Super 120B",
api: "openai-completions",
provider: "cloudflare-workers-ai",
baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
headers: {"User-Agent":"pi-coding-agent"},
compat: {"sendSessionAffinityHeaders":true},
reasoning: true,
input: ["text"],
cost: {
input: 0.5,
output: 1.5,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 256000,
maxTokens: 256000,
} satisfies Model<"openai-completions">,
"@cf/openai/gpt-oss-120b": {
id: "@cf/openai/gpt-oss-120b",
name: "GPT OSS 120B",
api: "openai-completions",
provider: "cloudflare-workers-ai",
baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
headers: {"User-Agent":"pi-coding-agent"},
compat: {"sendSessionAffinityHeaders":true},
reasoning: true,
input: ["text"],
cost: {
input: 0.35,
output: 0.75,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 128000,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"@cf/openai/gpt-oss-20b": {
id: "@cf/openai/gpt-oss-20b",
name: "GPT OSS 20B",
api: "openai-completions",
provider: "cloudflare-workers-ai",
baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
headers: {"User-Agent":"pi-coding-agent"},
compat: {"sendSessionAffinityHeaders":true},
reasoning: true,
input: ["text"],
cost: {
input: 0.2,
output: 0.3,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 128000,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"@cf/zai-org/glm-4.7-flash": {
id: "@cf/zai-org/glm-4.7-flash",
name: "GLM-4.7-Flash",
api: "openai-completions",
provider: "cloudflare-workers-ai",
baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
headers: {"User-Agent":"pi-coding-agent"},
compat: {"sendSessionAffinityHeaders":true},
reasoning: true,
input: ["text"],
cost: {
input: 0.06,
output: 0.4,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
},
} as const;

View File

@@ -0,0 +1,22 @@
import type { Model } from "../types.js";
/** Workers AI endpoint. `{CLOUDFLARE_ACCOUNT_ID}` is substituted at request time. */
export const CLOUDFLARE_WORKERS_AI_BASE_URL =
"https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1";
export function isCloudflareProvider(provider: string): boolean {
return provider === "cloudflare-workers-ai";
}
/** Substitute `{VAR}` placeholders in a Cloudflare baseUrl from process.env. */
export function resolveCloudflareBaseUrl(model: Model<"openai-completions">): string {
const url = model.baseUrl;
if (!url.includes("{")) return url;
return url.replace(/\{([A-Z_][A-Z0-9_]*)\}/g, (_match, name: string) => {
const value = process.env[name];
if (!value) {
throw new Error(`${name} is required for provider ${model.provider} but is not set.`);
}
return value;
});
}

View File

@@ -34,6 +34,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream.js";
import { headersToRecord } from "../utils/headers.js";
import { parseStreamingJson } from "../utils/json-parse.js";
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
import { isCloudflareProvider, resolveCloudflareBaseUrl } from "./cloudflare.js";
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
import { buildBaseOptions, clampReasoning } from "./simple-options.js";
import { transformMessages } from "./transform-messages.js";
@@ -456,7 +457,7 @@ function createClient(
return new OpenAI({
apiKey,
baseURL: model.baseUrl,
baseURL: isCloudflareProvider(model.provider) ? resolveCloudflareBaseUrl(model) : model.baseUrl,
dangerouslyAllowBrowser: true,
defaultHeaders: headers,
});
@@ -1022,6 +1023,7 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet
const baseUrl = model.baseUrl;
const isZai = provider === "zai" || baseUrl.includes("api.z.ai");
const isCloudflareWorkersAI = provider === "cloudflare-workers-ai" || baseUrl.includes("api.cloudflare.com");
const isNonStandard =
provider === "cerebras" ||
@@ -1032,7 +1034,8 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet
baseUrl.includes("deepseek.com") ||
isZai ||
provider === "opencode" ||
baseUrl.includes("opencode.ai");
baseUrl.includes("opencode.ai") ||
isCloudflareWorkersAI;
const useMaxTokens = baseUrl.includes("chutes.ai");

View File

@@ -41,7 +41,8 @@ export type KnownProvider =
| "fireworks"
| "opencode"
| "opencode-go"
| "kimi-coding";
| "kimi-coding"
| "cloudflare-workers-ai";
export type Provider = KnownProvider | string;
export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";

View File

@@ -84,6 +84,8 @@ const PROVIDER_MODEL_PAIRS: ProviderModelPair[] = [
{ provider: "xai", model: "grok-code-fast-1", label: "xai-grok-code-fast-1" },
// Cerebras
{ provider: "cerebras", model: "zai-glm-4.7", label: "cerebras-zai-glm-4.7" },
// Cloudflare Workers AI
{ provider: "cloudflare-workers-ai", model: "@cf/moonshotai/kimi-k2.6", label: "cloudflare-kimi-k2.6" },
// Groq
{ provider: "groq", model: "openai/gpt-oss-120b", label: "groq-gpt-oss-120b" },
// Hugging Face

View File

@@ -308,6 +308,29 @@ describe("AI Providers Empty Message Tests", () => {
});
});
describe.skipIf(!process.env.CLOUDFLARE_API_KEY || !process.env.CLOUDFLARE_ACCOUNT_ID)(
"Cloudflare Workers AI Provider Empty Messages",
() => {
const llm = getModel("cloudflare-workers-ai", "@cf/moonshotai/kimi-k2.6");
it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => {
await testEmptyMessage(llm);
});
it("should handle empty string content", { retry: 3, timeout: 30000 }, async () => {
await testEmptyStringMessage(llm);
});
it("should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => {
await testWhitespaceOnlyMessage(llm);
});
it("should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => {
await testEmptyAssistantMessage(llm);
});
},
);
describe.skipIf(!process.env.HF_TOKEN)("Hugging Face Provider Empty Messages", () => {
const llm = getModel("huggingface", "moonshotai/Kimi-K2.5");

View File

@@ -616,6 +616,33 @@ describe("Generate E2E Tests", () => {
});
});
describe.skipIf(!process.env.CLOUDFLARE_API_KEY || !process.env.CLOUDFLARE_ACCOUNT_ID)(
"Cloudflare Workers AI Provider (Kimi K2.6 via OpenAI Completions)",
() => {
const llm = getModel("cloudflare-workers-ai", "@cf/moonshotai/kimi-k2.6");
it("should complete basic text generation", { retry: 3 }, async () => {
await basicTextGeneration(llm);
});
it("should handle tool calling", { retry: 3 }, async () => {
await handleToolCall(llm);
});
it("should handle streaming", { retry: 3 }, async () => {
await handleStreaming(llm);
});
it("should handle thinking mode", { retry: 3 }, async () => {
await handleThinking(llm, { reasoningEffort: "medium" });
});
it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
await multiTurn(llm, { reasoningEffort: "medium" });
});
},
);
describe.skipIf(!process.env.HF_TOKEN)("Hugging Face Provider (Kimi-K2.5 via OpenAI Completions)", () => {
const llm = getModel("huggingface", "moonshotai/Kimi-K2.5");

View File

@@ -159,6 +159,17 @@ describe("Token Statistics on Abort", () => {
});
});
describe.skipIf(!process.env.CLOUDFLARE_API_KEY || !process.env.CLOUDFLARE_ACCOUNT_ID)(
"Cloudflare Workers AI Provider",
() => {
const llm = getModel("cloudflare-workers-ai", "@cf/moonshotai/kimi-k2.6");
it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => {
await testTokensOnAbort(llm);
});
},
);
describe.skipIf(!process.env.HF_TOKEN)("Hugging Face Provider", () => {
const llm = getModel("huggingface", "moonshotai/Kimi-K2.5");

View File

@@ -168,6 +168,21 @@ describe("Tool Call Without Result Tests", () => {
});
});
describe.skipIf(!process.env.CLOUDFLARE_API_KEY || !process.env.CLOUDFLARE_ACCOUNT_ID)(
"Cloudflare Workers AI Provider",
() => {
const model = getModel("cloudflare-workers-ai", "@cf/moonshotai/kimi-k2.6");
it(
"should filter out tool calls without corresponding tool results",
{ retry: 3, timeout: 30000 },
async () => {
await testToolCallWithoutResult(model);
},
);
},
);
describe.skipIf(!process.env.HF_TOKEN)("Hugging Face Provider", () => {
const model = getModel("huggingface", "moonshotai/Kimi-K2.5");

View File

@@ -306,6 +306,34 @@ describe("totalTokens field", () => {
);
});
// =========================================================================
// Cloudflare Workers AI
// =========================================================================
describe.skipIf(!process.env.CLOUDFLARE_API_KEY || !process.env.CLOUDFLARE_ACCOUNT_ID)(
"Cloudflare Workers AI",
() => {
it(
"@cf/moonshotai/kimi-k2.6 - should return totalTokens equal to sum of components",
{ retry: 3, timeout: 60000 },
async () => {
const llm = getModel("cloudflare-workers-ai", "@cf/moonshotai/kimi-k2.6");
console.log(`\nCloudflare Workers AI / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, {
apiKey: process.env.CLOUDFLARE_API_KEY,
});
logUsage("First request", first);
logUsage("Second request", second);
assertTotalTokensEqualsComponents(first);
assertTotalTokensEqualsComponents(second);
},
);
},
);
// =========================================================================
// Hugging Face
// =========================================================================

View File

@@ -611,6 +611,29 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => {
});
});
describe.skipIf(!process.env.CLOUDFLARE_API_KEY || !process.env.CLOUDFLARE_ACCOUNT_ID)(
"Cloudflare Workers AI Provider Unicode Handling",
() => {
const llm = getModel("cloudflare-workers-ai", "@cf/moonshotai/kimi-k2.6");
it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => {
await testEmojiInToolResults(llm);
});
it("should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => {
await testRealWorldLinkedInData(llm);
});
it(
"should handle unpaired high surrogate (0xD83D) in tool results",
{ retry: 3, timeout: 30000 },
async () => {
await testUnpairedHighSurrogate(llm);
},
);
},
);
describe.skipIf(!process.env.HF_TOKEN)("Hugging Face Provider Unicode Handling", () => {
const llm = getModel("huggingface", "moonshotai/Kimi-K2.5");

View File

@@ -114,6 +114,7 @@ For each built-in provider, pi maintains a list of tool-capable models, updated
- Mistral
- Groq
- Cerebras
- Cloudflare Workers AI
- xAI
- OpenRouter
- Vercel AI Gateway

View File

@@ -61,6 +61,7 @@ pi
| Mistral | `MISTRAL_API_KEY` | `mistral` |
| Groq | `GROQ_API_KEY` | `groq` |
| Cerebras | `CEREBRAS_API_KEY` | `cerebras` |
| Cloudflare Workers AI | `CLOUDFLARE_API_KEY` (+ `CLOUDFLARE_ACCOUNT_ID`) | `cloudflare-workers-ai` |
| xAI | `XAI_API_KEY` | `xai` |
| OpenRouter | `OPENROUTER_API_KEY` | `openrouter` |
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway` |
@@ -172,6 +173,18 @@ export AWS_BEDROCK_SKIP_AUTH=1
export AWS_BEDROCK_FORCE_HTTP1=1
```
### Cloudflare Workers AI
`CLOUDFLARE_API_KEY` can be set via `/login`. `CLOUDFLARE_ACCOUNT_ID` must be set as an environment variable.
```bash
export CLOUDFLARE_API_KEY=... # or use /login
export CLOUDFLARE_ACCOUNT_ID=...
pi --provider cloudflare-workers-ai --model "@cf/moonshotai/kimi-k2.6"
```
Pi automatically sets `x-session-affinity` for [prefix caching](https://developers.cloudflare.com/workers-ai/features/prompt-caching/) discounts.
### Google Vertex AI
Uses Application Default Credentials:

View File

@@ -316,6 +316,8 @@ ${chalk.bold("Environment Variables:")}
MINIMAX_API_KEY - MiniMax API key
OPENCODE_API_KEY - OpenCode Zen/OpenCode Go API key
KIMI_API_KEY - Kimi For Coding API key
CLOUDFLARE_API_KEY - Cloudflare API token (Workers AI)
CLOUDFLARE_ACCOUNT_ID - Cloudflare account id (required for Workers AI)
AWS_PROFILE - AWS profile for Amazon Bedrock
AWS_ACCESS_KEY_ID - AWS access key for Amazon Bedrock
AWS_SECRET_ACCESS_KEY - AWS secret key for Amazon Bedrock

View File

@@ -37,6 +37,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
opencode: "kimi-k2.6",
"opencode-go": "kimi-k2.6",
"kimi-coding": "kimi-for-coding",
"cloudflare-workers-ai": "@cf/moonshotai/kimi-k2.6",
};
export interface ScopedModel {

View File

@@ -182,6 +182,7 @@ const API_KEY_LOGIN_PROVIDERS: Record<string, string> = {
[BEDROCK_PROVIDER_ID]: "Amazon Bedrock",
"azure-openai-responses": "Azure OpenAI Responses",
cerebras: "Cerebras",
"cloudflare-workers-ai": "Cloudflare Workers AI",
deepseek: "DeepSeek",
fireworks: "Fireworks",
google: "Google Gemini",