xAI: normalize stale Grok transport to Responses

This commit is contained in:
huntharo
2026-03-27 14:11:50 -04:00
committed by Peter Steinberger
parent f0ce658fbb
commit 2765fdc2dd
3 changed files with 90 additions and 5 deletions

View File

@@ -10,6 +10,14 @@ function isOpenAIApiBaseUrl(baseUrl?: string): boolean {
return /^https?:\/\/api\.openai\.com(?:\/v1)?\/?$/i.test(trimmed);
}
function isXaiApiBaseUrl(baseUrl?: string): boolean {
const trimmed = baseUrl?.trim();
if (!trimmed) {
return false;
}
return /^https?:\/\/api\.x\.ai(?:\/v1)?\/?$/i.test(trimmed);
}
function normalizeOpenAITransport(params: { provider: string; model: Model<Api> }): Model<Api> {
if (normalizeProviderId(params.provider) !== "openai") {
return params.model;
@@ -29,11 +37,33 @@ function normalizeOpenAITransport(params: { provider: string; model: Model<Api>
} as Model<Api>;
}
function normalizeXaiTransport(params: { provider: string; model: Model<Api> }): Model<Api> {
if (normalizeProviderId(params.provider) !== "xai") {
return params.model;
}
const useResponsesTransport =
params.model.api === "openai-completions" &&
(!params.model.baseUrl || isXaiApiBaseUrl(params.model.baseUrl));
if (!useResponsesTransport) {
return params.model;
}
return {
...params.model,
api: "openai-responses",
} as Model<Api>;
}
export function applyBuiltInResolvedProviderTransportNormalization(params: {
provider: string;
model: Model<Api>;
}): Model<Api> {
return normalizeOpenAITransport(params);
return normalizeXaiTransport({
...params,
model: normalizeOpenAITransport(params),
});
}
export function normalizeResolvedProviderModel(params: {

View File

@@ -1002,4 +1002,62 @@ describe("resolveModel", () => {
baseUrl: "https://proxy.example.com/v1",
});
});
it("normalizes stale native xai completions transport to responses", () => {
mockDiscoveredModel(discoverModels, {
provider: "xai",
modelId: "grok-4.20-beta-latest-reasoning",
templateModel: buildForwardCompatTemplate({
id: "grok-4.20-beta-latest-reasoning",
name: "Grok 4.20 Beta Latest (Reasoning)",
provider: "xai",
api: "openai-completions",
baseUrl: "https://api.x.ai/v1",
}),
});
const result = resolveModelForTest("xai", "grok-4.20-beta-latest-reasoning", "/tmp/agent");
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "xai",
id: "grok-4.20-beta-latest-reasoning",
api: "openai-responses",
baseUrl: "https://api.x.ai/v1",
});
});
it("normalizes stale native xai completions transport after plugin model normalization", () => {
mockDiscoveredModel(discoverModels, {
provider: "xai",
modelId: "grok-4.20-beta-latest-reasoning",
templateModel: buildForwardCompatTemplate({
id: "grok-4.20-beta-latest-reasoning",
name: "Grok 4.20 Beta Latest (Reasoning)",
provider: "xai",
api: "openai-completions",
baseUrl: "https://api.x.ai/v1",
}),
});
const result = resolveModel("xai", "grok-4.20-beta-latest-reasoning", "/tmp/agent", undefined, {
authStorage: { mocked: true } as never,
modelRegistry: discoverModels({ mocked: true } as never, "/tmp/agent"),
runtimeHooks: {
buildProviderUnknownModelHintWithPlugin: () => undefined,
prepareProviderDynamicModel: async () => {},
runProviderDynamicModel: () => undefined,
normalizeProviderResolvedModelWithPlugin: ({ provider, context }) =>
provider === "xai" ? (context.model as never) : undefined,
},
});
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "xai",
id: "grok-4.20-beta-latest-reasoning",
api: "openai-responses",
baseUrl: "https://api.x.ai/v1",
});
});
});

View File

@@ -122,12 +122,9 @@ function normalizeResolvedModel(params: {
model: normalizedInputModel,
},
}) as Model<Api> | undefined;
if (pluginNormalized) {
return normalizeModelCompat(pluginNormalized);
}
return normalizeResolvedProviderModel({
provider: params.provider,
model: normalizedInputModel,
model: pluginNormalized ?? normalizedInputModel,
});
}