From 2ef88763e4aefe8feb35b471344908621be1ae4d Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:44:06 -0300 Subject: [PATCH] feat(xai): route xAI clients to Grok native /v1/responses endpoint (#6709) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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> --- .../features/6709-xai-responses-endpoint.md | 1 + .../config/providers/registry/xai/index.ts | 13 ++++++++++++- open-sse/executors/xai.ts | 19 +++++++++++++++++++ tests/unit/executor-xai.test.ts | 18 ++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 changelog.d/features/6709-xai-responses-endpoint.md diff --git a/changelog.d/features/6709-xai-responses-endpoint.md b/changelog.d/features/6709-xai-responses-endpoint.md new file mode 100644 index 0000000000..d828e6a7b8 --- /dev/null +++ b/changelog.d/features/6709-xai-responses-endpoint.md @@ -0,0 +1 @@ +- **feat(xai):** route xAI clients to Grok's native `/v1/responses` endpoint instead of the chat-completions bridge. (thanks @ryanngit) diff --git a/open-sse/config/providers/registry/xai/index.ts b/open-sse/config/providers/registry/xai/index.ts index 501fa4fcbf..f33e247080 100644 --- a/open-sse/config/providers/registry/xai/index.ts +++ b/open-sse/config/providers/registry/xai/index.ts @@ -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" }, ], diff --git a/open-sse/executors/xai.ts b/open-sse/executors/xai.ts index 9f806e2c5a..e5de5c037a 100644 --- a/open-sse/executors/xai.ts +++ b/open-sse/executors/xai.ts @@ -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; @@ -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, diff --git a/tests/unit/executor-xai.test.ts b/tests/unit/executor-xai.test.ts index df5d096ecd..f8a6031e5e 100644 --- a/tests/unit/executor-xai.test.ts +++ b/tests/unit/executor-xai.test.ts @@ -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"); +});