fix(ai): finalize cloudflare gateway provider support
This commit is contained in:
@@ -29,6 +29,7 @@ import { getModel } from "../src/models.js";
|
||||
import { completeSimple, getEnvApiKey } from "../src/stream.js";
|
||||
import type { Api, AssistantMessage, Message, Model, Tool, ToolResultMessage } from "../src/types.js";
|
||||
import { hasAzureOpenAICredentials } from "./azure-utils.js";
|
||||
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Simple tool for testing
|
||||
@@ -48,6 +49,7 @@ interface ProviderModelPair {
|
||||
model: string;
|
||||
label: string;
|
||||
apiOverride?: Api;
|
||||
upstreamApiKeyEnv?: string;
|
||||
}
|
||||
|
||||
const PROVIDER_MODEL_PAIRS: ProviderModelPair[] = [
|
||||
@@ -83,6 +85,24 @@ const PROVIDER_MODEL_PAIRS: ProviderModelPair[] = [
|
||||
{ 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" },
|
||||
// Cloudflare AI Gateway
|
||||
{
|
||||
provider: "cloudflare-ai-gateway",
|
||||
model: "workers-ai/@cf/moonshotai/kimi-k2.6",
|
||||
label: "cloudflare-gateway-kimi-k2.6",
|
||||
},
|
||||
{
|
||||
provider: "cloudflare-ai-gateway",
|
||||
model: "claude-sonnet-4-5",
|
||||
label: "cloudflare-gateway-claude-sonnet-4-5",
|
||||
upstreamApiKeyEnv: "ANTHROPIC_API_KEY",
|
||||
},
|
||||
{
|
||||
provider: "cloudflare-ai-gateway",
|
||||
model: "gpt-5.1",
|
||||
label: "cloudflare-gateway-gpt-5.1",
|
||||
upstreamApiKeyEnv: "OPENAI_API_KEY",
|
||||
},
|
||||
// Groq
|
||||
{ provider: "groq", model: "openai/gpt-oss-120b", label: "groq-gpt-oss-120b" },
|
||||
// Hugging Face
|
||||
@@ -127,18 +147,31 @@ async function getApiKey(provider: string): Promise<string | undefined> {
|
||||
/**
|
||||
* Synchronous check for API key availability (env vars only, for skipIf)
|
||||
*/
|
||||
function hasApiKey(provider: string): boolean {
|
||||
if (provider === "azure-openai-responses") {
|
||||
function hasApiKey(pair: ProviderModelPair): boolean {
|
||||
if (pair.provider === "azure-openai-responses") {
|
||||
return hasAzureOpenAICredentials();
|
||||
}
|
||||
return !!getEnvApiKey(provider);
|
||||
if (pair.provider === "cloudflare-workers-ai") {
|
||||
return hasCloudflareWorkersAICredentials();
|
||||
}
|
||||
if (pair.provider === "cloudflare-ai-gateway") {
|
||||
if (!hasCloudflareAiGatewayCredentials()) return false;
|
||||
return pair.upstreamApiKeyEnv ? !!process.env[pair.upstreamApiKeyEnv] : true;
|
||||
}
|
||||
return !!getEnvApiKey(pair.provider);
|
||||
}
|
||||
|
||||
function getHeaders(pair: ProviderModelPair): Record<string, string> | undefined {
|
||||
if (!pair.upstreamApiKeyEnv) return undefined;
|
||||
const upstreamApiKey = process.env[pair.upstreamApiKeyEnv];
|
||||
return upstreamApiKey ? { Authorization: `Bearer ${upstreamApiKey}` } : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any provider has API keys available (for skipIf at describe level)
|
||||
*/
|
||||
function hasAnyApiKey(): boolean {
|
||||
return PROVIDER_MODEL_PAIRS.some((pair) => hasApiKey(pair.provider));
|
||||
return PROVIDER_MODEL_PAIRS.some((pair) => hasApiKey(pair));
|
||||
}
|
||||
|
||||
function dumpFailurePayload(params: { label: string; error: string; payload?: unknown; messages: Message[] }): void {
|
||||
@@ -176,6 +209,7 @@ async function generateContext(
|
||||
};
|
||||
|
||||
const supportsReasoning = model.reasoning === true;
|
||||
const headers = getHeaders(pair);
|
||||
let lastPayload: unknown;
|
||||
let assistantResponse: AssistantMessage;
|
||||
try {
|
||||
@@ -189,6 +223,7 @@ async function generateContext(
|
||||
{
|
||||
apiKey,
|
||||
reasoning: supportsReasoning ? "high" : undefined,
|
||||
headers,
|
||||
onPayload: (payload) => {
|
||||
lastPayload = payload;
|
||||
},
|
||||
@@ -250,6 +285,7 @@ async function generateContext(
|
||||
{
|
||||
apiKey,
|
||||
reasoning: supportsReasoning ? "high" : undefined,
|
||||
headers,
|
||||
onPayload: (payload) => {
|
||||
lastPayload = payload;
|
||||
},
|
||||
@@ -296,7 +332,7 @@ describe.skipIf(!hasAnyApiKey())("Cross-Provider Handoff", () => {
|
||||
|
||||
for (const pair of PROVIDER_MODEL_PAIRS) {
|
||||
const apiKey = await getApiKey(pair.provider);
|
||||
if (!apiKey) {
|
||||
if (!apiKey || !hasApiKey(pair)) {
|
||||
console.log(`[${pair.label}] Skipping - no auth for ${pair.provider}`);
|
||||
continue;
|
||||
}
|
||||
@@ -344,7 +380,7 @@ describe.skipIf(!hasAnyApiKey())("Cross-Provider Handoff", () => {
|
||||
|
||||
for (const targetPair of availablePairs) {
|
||||
const apiKey = await getApiKey(targetPair.provider);
|
||||
if (!apiKey) {
|
||||
if (!apiKey || !hasApiKey(targetPair)) {
|
||||
console.log(`[Target: ${targetPair.label}] Skipping - no auth`);
|
||||
continue;
|
||||
}
|
||||
@@ -384,6 +420,7 @@ describe.skipIf(!hasAnyApiKey())("Cross-Provider Handoff", () => {
|
||||
? { ...baseModel, api: targetPair.apiOverride }
|
||||
: baseModel;
|
||||
const supportsReasoning = model.reasoning === true;
|
||||
const headers = getHeaders(targetPair);
|
||||
|
||||
console.log(
|
||||
`[Target: ${targetPair.label}] Testing with ${otherMessages.length} messages from other providers...`,
|
||||
@@ -401,6 +438,7 @@ describe.skipIf(!hasAnyApiKey())("Cross-Provider Handoff", () => {
|
||||
{
|
||||
apiKey,
|
||||
reasoning: supportsReasoning ? "high" : undefined,
|
||||
headers,
|
||||
onPayload: (payload) => {
|
||||
lastPayload = payload;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user