feat(xai): route xAI clients to Grok native /v1/responses endpoint (#6709)

* feat(xai): route xAI clients to Grok native /v1/responses endpoint

xAI ships a native /v1/responses endpoint (https://api.x.ai/v1/responses)
alongside /v1/chat/completions, but XaiExecutor extended BaseExecutor
without overriding buildUrl(), so every request always resolved to the
static chat-completions baseUrl regardless of target format — the last
genuinely-missing slice of decolua/9router#2439 (grok-build-0.1, the
reasoning-effort suffix routing, and bare grok-* routing were already
ported in prior cycles).

Add responsesBaseUrl to the xai registry entry and tag
grok-4.20-multi-agent-0309 (upstream's own Responses-only id) with
targetFormat: "openai-responses", mirroring the existing model-tag-driven
routing pattern already used by the gh executor (9router#102) and the
"openai" -pro heuristic in open-sse/executors/default.ts — the per-model
registry tag is the single source of truth that also drives chatCore's
body translation, so URL and body stay in lockstep. XaiExecutor.buildUrl
now checks getModelTargetFormat("xai", model) and resolves to the native
Responses endpoint only for tagged models, leaving every other grok-*
model on the existing chat-completions bridge.

TDD: tests/unit/executor-xai.test.ts adds a RED-then-GREEN case asserting
grok-4.20-multi-agent-0309 resolves to https://api.x.ai/v1/responses and
a control case asserting grok-4.3 still resolves to
https://api.x.ai/v1/chat/completions.

Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/2439

* chore(6709): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first)

---------

Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-10 19:44:06 -03:00
committed by GitHub
parent 28fcd418a4
commit 2ef88763e4
4 changed files with 50 additions and 1 deletions

View File

@@ -0,0 +1 @@
- **feat(xai):** route xAI clients to Grok's native `/v1/responses` endpoint instead of the chat-completions bridge. (thanks @ryanngit)

View File

@@ -6,12 +6,23 @@ export const xaiProvider: RegistryEntry = {
format: "openai",
executor: "xai",
baseUrl: "https://api.x.ai/v1/chat/completions",
// Port of decolua/9router#2439 (author: @ryanngit): xAI ships a native
// `/v1/responses` endpoint alongside `/v1/chat/completions`. Consumed by
// XaiExecutor.buildUrl (open-sse/executors/xai.ts) for models tagged
// targetFormat: "openai-responses" below.
responsesBaseUrl: "https://api.x.ai/v1/responses",
authType: "apikey",
authHeader: "bearer",
models: [
{ id: "grok-4.3", name: "Grok 4.3" },
{ id: "grok-build-0.1", name: "Grok Build 0.1", contextLength: 256000 },
{ id: "grok-4.20-multi-agent-0309", name: "Grok 4.20 Multi Agent" },
// Responses-only per upstream 9router#2439: xAI serves this id exclusively
// over its native /v1/responses endpoint.
{
id: "grok-4.20-multi-agent-0309",
name: "Grok 4.20 Multi Agent",
targetFormat: "openai-responses",
},
{ id: "grok-4.20-0309-reasoning", name: "Grok 4.20 Reasoning" },
{ id: "grok-4.20-0309-non-reasoning", name: "Grok 4.20" },
],

View File

@@ -1,5 +1,6 @@
import { BaseExecutor, type ProviderCredentials } from "./base.ts";
import { PROVIDERS } from "../config/constants.ts";
import { getModelTargetFormat } from "../config/providerModels.ts";
type JsonRecord = Record<string, unknown>;
@@ -51,6 +52,24 @@ export class XaiExecutor extends BaseExecutor {
super("xai", PROVIDERS.xai);
}
/**
* Port of decolua/9router#2439 (author: @ryanngit): xAI ships a native
* `/v1/responses` endpoint alongside `/v1/chat/completions`. Models tagged
* `targetFormat: "openai-responses"` in the registry (currently
* grok-4.20-multi-agent-0309, per upstream) resolve to that endpoint instead
* of the default chat-completions bridge. The per-model registry tag is the
* single source of truth — it also drives chatCore's body translation — so
* the URL stays in lockstep with the translated body, mirroring the gh
* executor's targetFormat-driven routing (9router#102) and the "openai"
* -pro heuristic in open-sse/executors/default.ts.
*/
buildUrl(model: string, _stream: boolean, _urlIndex = 0) {
if (getModelTargetFormat("xai", model) === "openai-responses") {
return this.config.responsesBaseUrl || this.config.baseUrl;
}
return this.config.baseUrl;
}
transformRequest(
model: string,
body: unknown,

View File

@@ -92,3 +92,21 @@ test("leaves a plain, unlisted model id and body unchanged (no suffix, not allow
assert.equal(out.reasoning_effort, undefined);
assert.deepEqual(out.messages, body.messages);
});
// Port of decolua/9router#2439 (author: @ryanngit): xAI ships a native
// `/v1/responses` endpoint. grok-4.20-multi-agent-0309 is tagged
// targetFormat: "openai-responses" in the registry (upstream's own tag) — it
// must resolve to xAI's native Responses URL, not the chat-completions
// bridge, mirroring the gh executor's targetFormat-driven routing (9router#102)
// and the "openai" -pro heuristic in open-sse/executors/default.ts.
test("XaiExecutor.buildUrl routes the Responses-tagged model (grok-4.20-multi-agent-0309) to xAI's native /v1/responses endpoint", () => {
const executor = new XaiExecutor();
const url = executor.buildUrl("grok-4.20-multi-agent-0309", true);
assert.equal(url, "https://api.x.ai/v1/responses");
});
test("XaiExecutor.buildUrl keeps a plain chat model (grok-4.3) on /v1/chat/completions", () => {
const executor = new XaiExecutor();
const url = executor.buildUrl("grok-4.3", true);
assert.equal(url, "https://api.x.ai/v1/chat/completions");
});