mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 03:00:21 +00:00
fix(providers): centralize compat endpoint detection (#60399)
This commit is contained in:
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user