fix(providers): centralize compat endpoint detection (#60399)

This commit is contained in:
Vincent Koc
2026-04-04 01:10:50 +09:00
committed by GitHub
parent fb4127082a
commit 7ed789d67d
4 changed files with 145 additions and 14 deletions

View File

@@ -2,6 +2,7 @@ import type { Model } from "@mariozechner/pi-ai";
import { describe, expect, it } from "vitest";
import {
buildOpenAIResponsesParams,
buildOpenAICompletionsParams,
buildTransportAwareSimpleStreamFn,
isTransportAwareApiSupported,
parseTransportChunkUsage,
@@ -304,4 +305,55 @@ describe("openai transport stream", () => {
expect(params.input?.[0]).toMatchObject({ role: "developer" });
});
it("uses system role for xAI default-route responses providers without relying on baseUrl host sniffing", () => {
const params = buildOpenAIResponsesParams(
{
id: "grok-4.1-fast",
name: "Grok 4.1 Fast",
api: "openai-responses",
provider: "xai",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 8192,
} satisfies Model<"openai-responses">,
{
systemPrompt: "system",
messages: [],
tools: [],
} as never,
undefined,
) as { input?: Array<{ role?: string }> };
expect(params.input?.[0]).toMatchObject({ role: "system" });
});
it("uses max_tokens for Chutes default-route completions providers without relying on baseUrl host sniffing", () => {
const params = buildOpenAICompletionsParams(
{
id: "zai-org/GLM-4.7-TEE",
name: "GLM 4.7 TEE",
api: "openai-completions",
provider: "chutes",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 8192,
} as never,
{
systemPrompt: "system",
messages: [],
tools: [],
} as never,
{
maxTokens: 2048,
} as never,
);
expect(params.max_tokens).toBe(2048);
expect(params).not.toHaveProperty("max_completion_tokens");
});
});

View File

@@ -1323,7 +1323,6 @@ async function processOpenAICompletionsStream(
function detectCompat(model: OpenAIModeModel) {
const provider = model.provider;
const baseUrl = model.baseUrl ?? "";
const capabilities = resolveProviderRequestCapabilities({
provider,
api: model.api,
@@ -1336,20 +1335,26 @@ function detectCompat(model: OpenAIModeModel) {
? (model.compat as { supportsStore?: boolean })
: undefined,
});
const isZai = provider === "zai" || baseUrl.includes("api.z.ai");
const endpointClass = capabilities.endpointClass;
const isDefaultRoute = endpointClass === "default";
const isZai = endpointClass === "zai-native" || (isDefaultRoute && provider === "zai");
const isNonStandard =
provider === "cerebras" ||
baseUrl.includes("cerebras.ai") ||
provider === "xai" ||
baseUrl.includes("api.x.ai") ||
baseUrl.includes("chutes.ai") ||
baseUrl.includes("deepseek.com") ||
endpointClass === "cerebras-native" ||
endpointClass === "chutes-native" ||
endpointClass === "deepseek-native" ||
endpointClass === "opencode-native" ||
endpointClass === "xai-native" ||
isZai ||
provider === "opencode" ||
baseUrl.includes("opencode.ai");
const useMaxTokens = baseUrl.includes("chutes.ai");
const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
const isGroq = provider === "groq" || baseUrl.includes("groq.com");
(isDefaultRoute &&
(provider === "cerebras" ||
provider === "chutes" ||
provider === "deepseek" ||
provider === "opencode" ||
provider === "xai"));
const useMaxTokens =
endpointClass === "chutes-native" || (isDefaultRoute && provider === "chutes");
const isGrok = endpointClass === "xai-native" || (isDefaultRoute && provider === "xai");
const isGroq = endpointClass === "groq-native" || (isDefaultRoute && provider === "groq");
const reasoningEffortMap: Record<string, string> =
isGroq && model.id === "qwen/qwen3-32b"
? {
@@ -1464,7 +1469,7 @@ function convertTools(tools: NonNullable<Context["tools"]>, compat: ReturnType<t
}));
}
function buildOpenAICompletionsParams(
export function buildOpenAICompletionsParams(
model: OpenAIModeModel,
context: Context,
options: OpenAICompletionsOptions | undefined,

View File

@@ -227,6 +227,37 @@ describe("provider attribution", () => {
});
});
it("classifies native OpenAI-compatible vendor hosts centrally", () => {
expect(resolveProviderEndpoint("https://api.x.ai/v1")).toMatchObject({
endpointClass: "xai-native",
hostname: "api.x.ai",
});
expect(resolveProviderEndpoint("https://api.z.ai/api/coding/paas/v4")).toMatchObject({
endpointClass: "zai-native",
hostname: "api.z.ai",
});
expect(resolveProviderEndpoint("https://api.deepseek.com")).toMatchObject({
endpointClass: "deepseek-native",
hostname: "api.deepseek.com",
});
expect(resolveProviderEndpoint("https://llm.chutes.ai/v1")).toMatchObject({
endpointClass: "chutes-native",
hostname: "llm.chutes.ai",
});
expect(resolveProviderEndpoint("https://api.groq.com/openai/v1")).toMatchObject({
endpointClass: "groq-native",
hostname: "api.groq.com",
});
expect(resolveProviderEndpoint("https://api.cerebras.ai/v1")).toMatchObject({
endpointClass: "cerebras-native",
hostname: "api.cerebras.ai",
});
expect(resolveProviderEndpoint("https://opencode.ai/api")).toMatchObject({
endpointClass: "opencode-native",
hostname: "opencode.ai",
});
});
it("treats OpenRouter-hosted Responses routes as explicit proxy-like endpoints", () => {
expect(
resolveProviderRequestPolicy({

View File

@@ -34,14 +34,21 @@ export type ProviderRequestCapability = "llm" | "audio" | "image" | "video" | "o
export type ProviderEndpointClass =
| "default"
| "anthropic-public"
| "cerebras-native"
| "chutes-native"
| "deepseek-native"
| "github-copilot-native"
| "groq-native"
| "mistral-public"
| "moonshot-native"
| "modelstudio-native"
| "openai-public"
| "openai-codex"
| "opencode-native"
| "azure-openai"
| "openrouter"
| "xai-native"
| "zai-native"
| "google-generative-ai"
| "google-vertex"
| "local"
@@ -206,15 +213,36 @@ export function resolveProviderEndpoint(
if (host === "api.mistral.ai") {
return { endpointClass: "mistral-public", hostname: host };
}
if (host === "api.cerebras.ai") {
return { endpointClass: "cerebras-native", hostname: host };
}
if (host === "llm.chutes.ai") {
return { endpointClass: "chutes-native", hostname: host };
}
if (host === "api.deepseek.com") {
return { endpointClass: "deepseek-native", hostname: host };
}
if (host.endsWith(".githubcopilot.com")) {
return { endpointClass: "github-copilot-native", hostname: host };
}
if (host === "api.groq.com") {
return { endpointClass: "groq-native", hostname: host };
}
if (host === "chatgpt.com") {
return { endpointClass: "openai-codex", hostname: host };
}
if (host === "opencode.ai" || host.endsWith(".opencode.ai")) {
return { endpointClass: "opencode-native", hostname: host };
}
if (host === "openrouter.ai" || host.endsWith(".openrouter.ai")) {
return { endpointClass: "openrouter", hostname: host };
}
if (host === "api.x.ai") {
return { endpointClass: "xai-native", hostname: host };
}
if (host === "api.z.ai") {
return { endpointClass: "zai-native", hostname: host };
}
if (host.endsWith(".openai.azure.com")) {
return { endpointClass: "azure-openai", hostname: host };
}
@@ -253,8 +281,16 @@ function resolveKnownProviderFamily(provider: string | undefined): string {
return "openrouter";
case "anthropic":
return "anthropic";
case "chutes":
return "chutes";
case "deepseek":
return "deepseek";
case "google":
return "google";
case "xai":
return "xai";
case "zai":
return "zai";
case "moonshot":
case "kimi":
return "moonshot";
@@ -501,14 +537,21 @@ export function resolveProviderRequestCapabilities(
const endpointClass = policy.endpointClass;
const isKnownNativeEndpoint =
endpointClass === "anthropic-public" ||
endpointClass === "cerebras-native" ||
endpointClass === "chutes-native" ||
endpointClass === "deepseek-native" ||
endpointClass === "github-copilot-native" ||
endpointClass === "groq-native" ||
endpointClass === "mistral-public" ||
endpointClass === "moonshot-native" ||
endpointClass === "modelstudio-native" ||
endpointClass === "openai-public" ||
endpointClass === "openai-codex" ||
endpointClass === "opencode-native" ||
endpointClass === "azure-openai" ||
endpointClass === "openrouter" ||
endpointClass === "xai-native" ||
endpointClass === "zai-native" ||
endpointClass === "google-generative-ai" ||
endpointClass === "google-vertex";