From 4533dd245f4fa31c9645b0a7058e209f64832f8b Mon Sep 17 00:00:00 2001
From: VXNCXNX <93332837+VXNCXNX@users.noreply.github.com>
Date: Thu, 6 Aug 2026 02:43:40 +0200
Subject: [PATCH] feat(providers): native xAI Agent Tools passthrough for
/v1/responses (#9111)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates green: static + changed tests + vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-213228-suite.log
---
.../8964-xai-agent-tools-passthrough.md | 1 +
config/quality/file-size-baseline.json | 6 +-
open-sse/config/cliFingerprints.ts | 1 +
open-sse/executors/xai.ts | 30 +-
open-sse/handlers/chatCore.ts | 26 +-
.../handlers/chatCore/passthroughHelpers.ts | 32 +-
open-sse/handlers/chatCore/requestFormat.ts | 25 +-
open-sse/handlers/chatCore/targetFormat.ts | 5 +-
open-sse/handlers/responseSanitizer.ts | 13 +
.../translator/request/openai-responses.ts | 7 +-
.../request/openai-responses/helpers.ts | 1 +
open-sse/utils/responsesEndpoint.ts | 5 +
open-sse/utils/usageTracking.ts | 4 +
.../chatcore-execution-credentials.test.ts | 10 +
tests/unit/chatcore-request-format.test.ts | 29 +-
tests/unit/web-search-fallback-format.test.ts | 13 +
.../unit/xai-agent-tools-passthrough.test.ts | 394 ++++++++++++++++++
17 files changed, 569 insertions(+), 33 deletions(-)
create mode 100644 changelog.d/features/8964-xai-agent-tools-passthrough.md
create mode 100644 open-sse/utils/responsesEndpoint.ts
create mode 100644 tests/unit/xai-agent-tools-passthrough.test.ts
diff --git a/changelog.d/features/8964-xai-agent-tools-passthrough.md b/changelog.d/features/8964-xai-agent-tools-passthrough.md
new file mode 100644
index 0000000000..62536adb43
--- /dev/null
+++ b/changelog.d/features/8964-xai-agent-tools-passthrough.md
@@ -0,0 +1 @@
+- **feat(providers):** native xAI Agent Tools passthrough on `/v1/responses` for `xai` / `xai-oauth` (`xao`) — forward `web_search` + `x_search` to `api.x.ai` instead of rewriting or rejecting them ([#8964](https://github.com/diegosouzapw/OmniRoute/issues/8964))
diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json
index eda3bfc98f..4034c147c2 100644
--- a/config/quality/file-size-baseline.json
+++ b/config/quality/file-size-baseline.json
@@ -350,9 +350,9 @@
"open-sse/executors/deepseek-web.ts": 1148,
"open-sse/executors/grok-web.ts": 1044,
"open-sse/executors/muse-spark-web.ts": 1405,
- "open-sse/handlers/chatCore.ts": 5020,
+ "open-sse/handlers/chatCore.ts": 5034,
"open-sse/handlers/imageGeneration.ts": 3101,
- "open-sse/handlers/responseSanitizer.ts": 1115,
+ "open-sse/handlers/responseSanitizer.ts": 1128,
"open-sse/handlers/search.ts": 1536,
"open-sse/handlers/videoGeneration.ts": 1063,
"open-sse/mcp-server/schemas/tools.ts": 1505,
@@ -416,5 +416,7 @@
"_rebaseline_2026_07_28_8861_xiaomi_token_plan": "PR #8861 (feat/xiaomi-token-plan-protocol-selector) own growth: EditConnectionModal.tsx 1283->1316 (+33 = the per-connection API-protocol selector field) and open-sse/executors/base.ts 1540->1562 (+22 = alternate-format resolution at the existing buildUrl/headers chokepoint). Both are irreducible wiring at existing call sites.",
"_rebaseline_2026_07_28_8863_firefly_detail_level": "PR #8863 (fix/adobe-firefly-gpt-detail-level-max) own growth: adobeFireflyClient.ts 2317->2322 (+5 = gpt-image detailLevel defaulting to maximal at the existing payload-build site). Covered by tests/unit/adobe-firefly.test.ts.",
"_rebaseline_2026_07_29_8281_home_quickstart_prefetch": "Release v3.8.49 base-red fix (no PR — captain sweep): src/app/(dashboard)/dashboard/HomePageClient.tsx 1377->1381 (+4). #8292 added prefetch={false} to the sidebar but left /home's five quick-start Links prefetching, so first paint still fired 12 speculative RSC requests — caught by navigation.spec.ts only after the e2e helper bug (APP_ROUTE_PATTERN missing /home) was repaired in the same cycle. Growth is the five prefetch attributes; it was offset first by extracting the repeated className literals (INLINE_LINK x4, DOCS_LINK x1), which collapsed five wrapped blocks back to one line each — a naive fix measured 1391. Guard: tests/unit/sidebar-prefetch-policy-8281.test.ts.",
+ "_rebaseline_2026_08_01_8964_xai_agent_tools": "PR #8964 own growth: chatCore.ts 5020->5034 at the existing native-passthrough chokepoint. Adds xAI Agent Tools passthrough for /v1/responses (xai/xai-oauth/xao): resolve nativeXaiResponsesPassthrough, force openai-responses targetFormat, stamp body marker, and OR into the existing nativeCodexPassthrough sites (web-search bypass + requestEndpointPath). Leaf logic in passthroughHelpers, responsesEndpoint, targetFormat, xai executor, responseSanitizer, usageTracking. Cohesive wiring at the Codex passthrough boundary.",
+ "_rebaseline_2026_08_01_8964_response_sanitizer": "PR #8964 own growth: responseSanitizer.ts 1115->1128. Keep cost_in_usd_ticks / server_side_tool_usage(_details) through sanitizeResponsesApiResponse allowlists so native xAI tool responses retain usage.",
"_rebaseline_2026_08_02_v3850_agentrouter_responses": "Release v3.8.50 AgentRouter/Codex compatibility reconciliation. open-sse/executors/base.ts 1562->1578: #9190 wires AgentRouter's selected Claude/OpenAI/Responses protocol through the existing executor URL, auth, identity-header and fingerprint chokepoints; the reusable alternate resolver remains outside base.ts. open-sse/utils/stream.ts 2887->2889: #9213 evaluates Responses ID and usage normalization independently so response.completed always receives finite usage.total_tokens instead of short-circuiting after an ID rewrite. tests/unit/chatcore-translation-paths.test.ts 2769->2776: #9191 updates the existing Claude-Code bridge assertions for the dynamic AgentRouter wire image. PR #9224 offsets its own chatCore growth by extracting the AgentRouter protocol decisions into chatCore/agentRouterProtocol.ts, leaving chatCore below its frozen ceiling. Covered by agentrouter executor/chatCore protocol tests, chatcore translation-path tests, and responses-commentary-passthrough tests."
}
diff --git a/open-sse/config/cliFingerprints.ts b/open-sse/config/cliFingerprints.ts
index da65fce35b..b219cd1363 100644
--- a/open-sse/config/cliFingerprints.ts
+++ b/open-sse/config/cliFingerprints.ts
@@ -271,6 +271,7 @@ function stripInternalBodyFields(body: unknown): unknown {
const record = body as Record;
delete record._claudeCodeRequiresLowercaseToolNames;
delete record._nativeCodexPassthrough;
+ delete record._nativeXaiResponsesPassthrough;
delete record._omnirouteResponsesStore;
return body;
}
diff --git a/open-sse/executors/xai.ts b/open-sse/executors/xai.ts
index 5fc6217729..f9b92fa1f6 100644
--- a/open-sse/executors/xai.ts
+++ b/open-sse/executors/xai.ts
@@ -1,6 +1,7 @@
import { BaseExecutor, type ExecutorLog, type ProviderCredentials } from "./base.ts";
import { PROVIDERS } from "../config/constants.ts";
import { getModelTargetFormat } from "../config/providerModels.ts";
+import { isResponsesEndpointPath } from "../utils/responsesEndpoint.ts";
type JsonRecord = Record;
@@ -52,21 +53,18 @@ export class XaiExecutor extends BaseExecutor {
super(provider, PROVIDERS[provider]);
}
- /**
- * 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) {
+ buildUrl(
+ model: string,
+ _stream: boolean,
+ _urlIndex = 0,
+ credentials: ProviderCredentials | null = null
+ ) {
if (getModelTargetFormat(this.provider, model) === "openai-responses") {
return this.config.responsesBaseUrl || this.config.baseUrl;
}
+ if (isResponsesEndpointPath(credentials?.requestEndpointPath)) {
+ return this.config.responsesBaseUrl || this.config.baseUrl;
+ }
return this.config.baseUrl;
}
@@ -127,6 +125,14 @@ export class XaiExecutor extends BaseExecutor {
if (!record) return cleaned;
const out: JsonRecord = { ...record };
+ const nativeXaiPassthrough = record._nativeXaiResponsesPassthrough === true;
+ delete out._nativeXaiResponsesPassthrough;
+ delete out._nativeCodexPassthrough;
+
+ if (nativeXaiPassthrough || getModelTargetFormat(this.provider, model) === "openai-responses") {
+ return out;
+ }
+
let modelId = typeof out.model === "string" ? out.model : model;
let suffixEffort: string | null = null;
diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts
index 4eaa24ff57..7421d89628 100644
--- a/open-sse/handlers/chatCore.ts
+++ b/open-sse/handlers/chatCore.ts
@@ -38,6 +38,8 @@ import {
} from "./chatCore/executorHelpers.ts";
import {
shouldUseNativeCodexPassthrough,
+ shouldUseNativeXaiResponsesPassthrough,
+ stampNativeResponsesPassthroughBody,
redactPassthroughThinkingSignatures,
isClaudeCodeSemanticPassthroughRequest,
} from "./chatCore/passthroughHelpers.ts";
@@ -56,6 +58,7 @@ import {
// symbols from chatCore.ts (tests, sibling modules) keep resolving after the split.
export {
shouldUseNativeCodexPassthrough,
+ shouldUseNativeXaiResponsesPassthrough,
redactPassthroughThinkingSignatures,
isClaudeCodeSemanticPassthroughRequest,
buildStreamingResponseHeaders,
@@ -628,6 +631,7 @@ export async function handleChatCore({
sourceFormat,
isResponsesEndpoint,
nativeCodexPassthrough,
+ nativeXaiResponsesPassthrough,
isDroidCLI,
isOpencodeClient,
copilotCompatibleReasoning,
@@ -748,7 +752,9 @@ export async function handleChatCore({
sourceFormat,
customModelTargetFormat,
providerSpecificData: credentials?.providerSpecificData,
+ nativeXaiResponsesPassthrough,
});
+ const nativeResponsesPassthrough = nativeCodexPassthrough || nativeXaiResponsesPassthrough;
const initialProviderRequest =
body && typeof body === "object" && !Array.isArray(body)
@@ -788,7 +794,7 @@ export async function handleChatCore({
provider,
sourceFormat,
targetFormat,
- nativeCodexPassthrough,
+ nativeCodexPassthrough: nativeResponsesPassthrough,
interceptSearchOverride,
});
if (webSearchFallbackPlan.enabled) {
@@ -806,7 +812,7 @@ export async function handleChatCore({
provider,
sourceFormat,
targetFormat,
- nativeCodexPassthrough,
+ nativeCodexPassthrough: nativeResponsesPassthrough,
interceptFetchOverride,
});
if (webFetchFallbackPlan.enabled) {
@@ -1976,9 +1982,17 @@ export async function handleChatCore({
) => normalizeClaudeUpstreamMessagesFor(payload, options, log);
try {
- if (nativeCodexPassthrough) {
- translatedBody = { ...body, _nativeCodexPassthrough: true };
- log?.debug?.("FORMAT", "native codex passthrough enabled");
+ if (nativeResponsesPassthrough) {
+ translatedBody = stampNativeResponsesPassthroughBody(
+ body,
+ nativeCodexPassthrough ? "codex" : "xai"
+ );
+ log?.debug?.(
+ "FORMAT",
+ nativeCodexPassthrough
+ ? "native codex passthrough enabled"
+ : "native xAI Responses Agent Tools passthrough enabled"
+ );
} else if (isClaudeCodeCompatible) {
let normalizedForCc = { ...body };
@@ -2616,7 +2630,7 @@ export async function handleChatCore({
const getExecutionCredentials = () =>
resolveExecutionCredentialsFor({
credentials,
- nativeCodexPassthrough,
+ nativeCodexPassthrough: nativeResponsesPassthrough,
endpointPath,
targetFormat,
provider,
diff --git a/open-sse/handlers/chatCore/passthroughHelpers.ts b/open-sse/handlers/chatCore/passthroughHelpers.ts
index 3c0731c2b1..e644878329 100644
--- a/open-sse/handlers/chatCore/passthroughHelpers.ts
+++ b/open-sse/handlers/chatCore/passthroughHelpers.ts
@@ -1,7 +1,12 @@
import { FORMATS } from "../../translator/formats.ts";
import { isClaudeCodeCompatibleProvider } from "../../services/claudeCodeCompatible.ts";
+import { isResponsesEndpointPath } from "../../utils/responsesEndpoint.ts";
import { getHeaderValueCaseInsensitive } from "./headers.ts";
+export { isResponsesEndpointPath };
+
+export const XAI_API_PROVIDERS = new Set(["xai", "xai-oauth", "xao"]);
+
export function shouldUseNativeCodexPassthrough({
provider,
sourceFormat,
@@ -13,10 +18,29 @@ export function shouldUseNativeCodexPassthrough({
}): boolean {
if (provider !== "codex") return false;
if (sourceFormat !== FORMATS.OPENAI_RESPONSES) return false;
- let normalizedEndpoint = String(endpointPath || "");
- while (normalizedEndpoint.endsWith("/")) normalizedEndpoint = normalizedEndpoint.slice(0, -1);
- const segments = normalizedEndpoint.split("/");
- return segments.includes("responses");
+ return isResponsesEndpointPath(endpointPath);
+}
+
+export function shouldUseNativeXaiResponsesPassthrough({
+ provider,
+ sourceFormat,
+ endpointPath,
+}: {
+ provider?: string | null;
+ sourceFormat?: string | null;
+ endpointPath?: string | null;
+}): boolean {
+ if (!provider || !XAI_API_PROVIDERS.has(provider)) return false;
+ if (sourceFormat !== FORMATS.OPENAI_RESPONSES) return false;
+ return isResponsesEndpointPath(endpointPath);
+}
+
+export function stampNativeResponsesPassthroughBody(
+ body: Record,
+ mode: "codex" | "xai"
+): Record {
+ if (mode === "codex") return { ...body, _nativeCodexPassthrough: true };
+ return { ...body, _nativeXaiResponsesPassthrough: true };
}
/**
diff --git a/open-sse/handlers/chatCore/requestFormat.ts b/open-sse/handlers/chatCore/requestFormat.ts
index fa9e9194fb..d5ce5ad23b 100644
--- a/open-sse/handlers/chatCore/requestFormat.ts
+++ b/open-sse/handlers/chatCore/requestFormat.ts
@@ -11,7 +11,10 @@
*/
import { detectFormatFromEndpoint } from "../../services/provider.ts";
-import { shouldUseNativeCodexPassthrough } from "./passthroughHelpers.ts";
+import {
+ shouldUseNativeCodexPassthrough,
+ shouldUseNativeXaiResponsesPassthrough,
+} from "./passthroughHelpers.ts";
import { FORMATS } from "../../translator/formats.ts";
/** True when the request originates from a Copilot client (matched by user-agent or any header). */
@@ -49,13 +52,19 @@ function isOpencodeClient(
if (headers instanceof Headers) {
for (const [key, value] of headers as unknown as Iterable<[string, string]>) {
- if (matchesHeaderKey(key) || (key.toLowerCase() === "user-agent" && matchesUserAgent(value))) {
+ if (
+ matchesHeaderKey(key) ||
+ (key.toLowerCase() === "user-agent" && matchesUserAgent(value))
+ ) {
return true;
}
}
} else if (headers && typeof headers === "object") {
for (const [key, value] of Object.entries(headers)) {
- if (matchesHeaderKey(key) || (key.toLowerCase() === "user-agent" && matchesUserAgent(value))) {
+ if (
+ matchesHeaderKey(key) ||
+ (key.toLowerCase() === "user-agent" && matchesUserAgent(value))
+ ) {
return true;
}
}
@@ -71,9 +80,7 @@ function isOpencodeClient(
*/
export function resolveChatCoreRequestFormat(opts: {
clientRawRequest:
- | { endpoint?: unknown; headers?: Headers | Record | null }
- | null
- | undefined;
+ { endpoint?: unknown; headers?: Headers | Record | null } | null | undefined;
body: unknown;
provider: string | null | undefined;
userAgent: string | null | undefined;
@@ -88,6 +95,11 @@ export function resolveChatCoreRequestFormat(opts: {
sourceFormat,
endpointPath,
});
+ const nativeXaiResponsesPassthrough = shouldUseNativeXaiResponsesPassthrough({
+ provider,
+ sourceFormat,
+ endpointPath,
+ });
const isDroidCLI =
userAgent?.toLowerCase().includes("droid") || userAgent?.toLowerCase().includes("codex-cli");
const copilotCompatibleReasoning = isCopilotClient(clientRawRequest?.headers, userAgent);
@@ -101,6 +113,7 @@ export function resolveChatCoreRequestFormat(opts: {
sourceFormat,
isResponsesEndpoint,
nativeCodexPassthrough,
+ nativeXaiResponsesPassthrough,
isDroidCLI,
copilotCompatibleReasoning,
isOpencodeClient: isOpencodeClientRequest,
diff --git a/open-sse/handlers/chatCore/targetFormat.ts b/open-sse/handlers/chatCore/targetFormat.ts
index 9bb2f0da7a..e5f00eb81a 100644
--- a/open-sse/handlers/chatCore/targetFormat.ts
+++ b/open-sse/handlers/chatCore/targetFormat.ts
@@ -22,6 +22,7 @@ export function resolveChatCoreTargetFormat(opts: {
sourceFormat?: string;
customModelTargetFormat: string | undefined;
providerSpecificData: unknown;
+ nativeXaiResponsesPassthrough?: boolean;
}) {
const {
provider,
@@ -30,6 +31,7 @@ export function resolveChatCoreTargetFormat(opts: {
sourceFormat,
customModelTargetFormat,
providerSpecificData,
+ nativeXaiResponsesPassthrough = false,
} = opts;
const alias = PROVIDER_ID_TO_ALIAS[provider] || provider;
const modelTargetFormat = getModelTargetFormat(alias, resolvedModel);
@@ -44,13 +46,14 @@ export function resolveChatCoreTargetFormat(opts: {
sourceFormat === FORMATS.CLAUDE)
? sourceFormat
: undefined;
- const targetFormat =
+ let targetFormat =
apiFormat === "responses"
? FORMATS.OPENAI_RESPONSES
: modelTargetFormat ||
customModelTargetFormat ||
inferredAgentRouterTargetFormat ||
getTargetFormat(provider, providerSpecificData);
+ if (nativeXaiResponsesPassthrough) targetFormat = FORMATS.OPENAI_RESPONSES;
return { alias, targetFormat };
}
diff --git a/open-sse/handlers/responseSanitizer.ts b/open-sse/handlers/responseSanitizer.ts
index 140011f64e..77a126ed34 100644
--- a/open-sse/handlers/responseSanitizer.ts
+++ b/open-sse/handlers/responseSanitizer.ts
@@ -42,8 +42,17 @@ const ALLOWED_RESPONSES_USAGE_FIELDS = new Set([
"input_tokens_details",
"output_tokens_details",
"estimated",
+ "cost_in_usd_ticks",
+ "server_side_tool_usage_details",
+ "server_side_tool_usage",
]);
+const RESPONSES_EXTRA_TOP_LEVEL_FIELDS = [
+ "server_side_tool_usage_details",
+ "server_side_tool_usage",
+ "cost_in_usd_ticks",
+] as const;
+
type JsonRecord = Record;
type ParseOptions = { parseTextualReasoningTags?: boolean };
@@ -355,6 +364,10 @@ export function sanitizeResponsesApiResponse(body: unknown): unknown {
sanitized.usage = sanitizeResponsesUsage(responseRoot.usage);
}
+ for (const key of RESPONSES_EXTRA_TOP_LEVEL_FIELDS) {
+ if (responseRoot[key] !== undefined) sanitized[key] = responseRoot[key];
+ }
+
return sanitized;
}
diff --git a/open-sse/translator/request/openai-responses.ts b/open-sse/translator/request/openai-responses.ts
index ab4182b746..4b2186d552 100644
--- a/open-sse/translator/request/openai-responses.ts
+++ b/open-sse/translator/request/openai-responses.ts
@@ -20,6 +20,7 @@ import {
RESPONSES_STORE_MARKER,
COPILOT_REASONING_SUMMARY_MARKER,
WEB_SEARCH_TOOL_TYPES,
+ X_SEARCH_TOOL_TYPES,
TOOL_SEARCH_TOOL_TYPES,
IMAGE_GENERATION_TOOL_TYPES,
toRecord,
@@ -103,7 +104,7 @@ export function openaiResponsesToOpenAIRequest(
// namespace tools (MCP tool groups used by Codex/OpenAI Responses API), and web_search server tools
// (Anthropic versioned: web_search_20250305, web_search_20250101, etc. — or plain web_search).
// tool_search is a Responses API built-in sent by newer Codex clients; silently skip it here
- // (it will be filtered out during tools conversion below).
+ // (it will be filtered out during tools conversion below). x_search (#8964) same pattern.
if (
toolType &&
toolType !== "function" &&
@@ -112,6 +113,7 @@ export function openaiResponsesToOpenAIRequest(
toolType !== "namespace" &&
toolType !== "local_shell" &&
!WEB_SEARCH_TOOL_TYPES.test(toolType) &&
+ !X_SEARCH_TOOL_TYPES.test(toolType) &&
!TOOL_SEARCH_TOOL_TYPES.test(toolType) &&
!IMAGE_GENERATION_TOOL_TYPES.test(toolType) &&
!tool.function
@@ -531,6 +533,9 @@ export function openaiResponsesToOpenAIRequest(
if (WEB_SEARCH_TOOL_TYPES.test(toolType)) {
return toolValue;
}
+ if (X_SEARCH_TOOL_TYPES.test(toolType)) {
+ return [];
+ }
// local_shell is a Responses API built-in (Codex CLI injects it for shell
// execution). Non-OpenAI upstreams (Kiro/Claude) have no local_shell type,
// so map it to a regular "shell" function tool. The response translator
diff --git a/open-sse/translator/request/openai-responses/helpers.ts b/open-sse/translator/request/openai-responses/helpers.ts
index a65a7fe86b..ac5f8b746d 100644
--- a/open-sse/translator/request/openai-responses/helpers.ts
+++ b/open-sse/translator/request/openai-responses/helpers.ts
@@ -7,6 +7,7 @@ export const COPILOT_REASONING_SUMMARY_MARKER = "_omnirouteCopilotReasoningSumma
// Forward-compatible regex: matches web_search, web_search_20250305, and future versioned names.
export const WEB_SEARCH_TOOL_TYPES = /^web_search/;
+export const X_SEARCH_TOOL_TYPES = /^x_search/;
// tool_search is a Responses API built-in sent by newer Codex clients; it has no Chat Completions
// equivalent and must be silently dropped (not rejected with 400).
export const TOOL_SEARCH_TOOL_TYPES = /^tool_search/;
diff --git a/open-sse/utils/responsesEndpoint.ts b/open-sse/utils/responsesEndpoint.ts
new file mode 100644
index 0000000000..216152f483
--- /dev/null
+++ b/open-sse/utils/responsesEndpoint.ts
@@ -0,0 +1,5 @@
+export function isResponsesEndpointPath(endpointPath?: string | null): boolean {
+ let normalizedEndpoint = String(endpointPath || "");
+ while (normalizedEndpoint.endsWith("/")) normalizedEndpoint = normalizedEndpoint.slice(0, -1);
+ return normalizedEndpoint.split("/").includes("responses");
+}
diff --git a/open-sse/utils/usageTracking.ts b/open-sse/utils/usageTracking.ts
index 90f457b77e..f0d6990a90 100644
--- a/open-sse/utils/usageTracking.ts
+++ b/open-sse/utils/usageTracking.ts
@@ -232,9 +232,13 @@ export function filterUsageForFormat(usage, targetFormat) {
[FORMATS.OPENAI_RESPONSES]: [
"input_tokens",
"output_tokens",
+ "total_tokens",
"input_tokens_details",
"output_tokens_details",
"estimated",
+ "cost_in_usd_ticks",
+ "server_side_tool_usage_details",
+ "server_side_tool_usage",
],
// OpenAI format (default for OPENAI, CODEX, KIRO, etc.)
default: [
diff --git a/tests/unit/chatcore-execution-credentials.test.ts b/tests/unit/chatcore-execution-credentials.test.ts
index 692a662fbc..b8faeccf90 100644
--- a/tests/unit/chatcore-execution-credentials.test.ts
+++ b/tests/unit/chatcore-execution-credentials.test.ts
@@ -33,6 +33,16 @@ test("native Codex passthrough injects requestEndpointPath", () => {
assert.equal(out.requestEndpointPath, "/v1/responses");
});
+test("native Responses passthrough (xAI via codex flag) injects requestEndpointPath (#8964)", () => {
+ // chatCore ORs xAI into nativeCodexPassthrough before calling this helper.
+ const out = resolveExecutionCredentials({
+ ...base,
+ provider: "xai-oauth",
+ nativeCodexPassthrough: true,
+ }) as Record;
+ assert.equal(out.requestEndpointPath, "/v1/responses");
+});
+
test("azure-ai + responses target forces apiType=responses and the upstream marker", () => {
const out = resolveExecutionCredentials({
...base,
diff --git a/tests/unit/chatcore-request-format.test.ts b/tests/unit/chatcore-request-format.test.ts
index 064a2384f2..52c9a154bf 100644
--- a/tests/unit/chatcore-request-format.test.ts
+++ b/tests/unit/chatcore-request-format.test.ts
@@ -10,7 +10,11 @@ import { resolveChatCoreRequestFormat } from "../../open-sse/handlers/chatCore/r
import { shouldUseNativeCodexPassthrough } from "../../open-sse/handlers/chatCore/passthroughHelpers.ts";
import { FORMATS } from "../../open-sse/translator/formats.ts";
-const base = { body: { messages: [{ role: "user", content: "hi" }] }, provider: "openai", userAgent: "unit-test" };
+const base = {
+ body: { messages: [{ role: "user", content: "hi" }] },
+ provider: "openai",
+ userAgent: "unit-test",
+};
test("chat/completions endpoint → openai source, not a responses endpoint, no downgrade", () => {
const r = resolveChatCoreRequestFormat({
@@ -38,6 +42,29 @@ test("/responses endpoint → openai-responses source + isResponsesEndpoint, kep
assert.equal(r.clientResponseFormat, FORMATS.OPENAI_RESPONSES);
});
+test("xAI oauth on /responses enables nativeXaiResponsesPassthrough (#8964)", () => {
+ const r = resolveChatCoreRequestFormat({
+ body: { input: "x", tools: [{ type: "web_search" }, { type: "x_search" }] },
+ provider: "xai-oauth",
+ userAgent: "unit-test",
+ clientRawRequest: { endpoint: "/v1/responses", headers: new Headers() },
+ });
+ assert.equal(r.sourceFormat, FORMATS.OPENAI_RESPONSES);
+ assert.equal(r.isResponsesEndpoint, true);
+ assert.equal(r.nativeXaiResponsesPassthrough, true);
+ assert.equal(r.nativeCodexPassthrough, false);
+});
+
+test("xao alias on /responses enables nativeXaiResponsesPassthrough (#8964)", () => {
+ const r = resolveChatCoreRequestFormat({
+ body: { input: "x" },
+ provider: "xao",
+ userAgent: "unit-test",
+ clientRawRequest: { endpoint: "/v1/responses", headers: new Headers() },
+ });
+ assert.equal(r.nativeXaiResponsesPassthrough, true);
+});
+
test("Responses-shaped body on a /chat/completions endpoint downgrades clientResponseFormat to openai", () => {
const r = resolveChatCoreRequestFormat({
body: { input: "describe" }, // input + no messages → openai-responses via body
diff --git a/tests/unit/web-search-fallback-format.test.ts b/tests/unit/web-search-fallback-format.test.ts
index 39835e01dc..a4809780bc 100644
--- a/tests/unit/web-search-fallback-format.test.ts
+++ b/tests/unit/web-search-fallback-format.test.ts
@@ -90,6 +90,19 @@ test("bypass predicate: true for native Codex passthrough", () => {
);
});
+test("bypass predicate: true when native Responses passthrough flag is set (#8964 xAI)", () => {
+ // Callers OR codex|xai into nativeCodexPassthrough (existing flag = "any native lane").
+ assert.equal(
+ supportsNativeWebSearchFallbackBypass({
+ provider: "xai-oauth",
+ sourceFormat: "openai-responses",
+ targetFormat: "openai-responses",
+ nativeCodexPassthrough: true,
+ }),
+ true
+ );
+});
+
test("bypass predicate: true for Gemini target", () => {
assert.equal(
supportsNativeWebSearchFallbackBypass({
diff --git a/tests/unit/xai-agent-tools-passthrough.test.ts b/tests/unit/xai-agent-tools-passthrough.test.ts
new file mode 100644
index 0000000000..ac9daa408f
--- /dev/null
+++ b/tests/unit/xai-agent-tools-passthrough.test.ts
@@ -0,0 +1,394 @@
+/**
+ * #8964 — native xAI Agent Tools passthrough for /v1/responses
+ */
+import test from "node:test";
+import assert from "node:assert/strict";
+
+const {
+ shouldUseNativeXaiResponsesPassthrough,
+ shouldUseNativeCodexPassthrough,
+ isResponsesEndpointPath,
+ stampNativeResponsesPassthroughBody,
+ XAI_API_PROVIDERS,
+} = await import("../../open-sse/handlers/chatCore/passthroughHelpers.ts");
+
+const {
+ supportsNativeWebSearchFallbackBypass,
+ prepareWebSearchFallbackBody,
+ OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME,
+} = await import("../../open-sse/services/webSearchFallback.ts");
+
+const { openaiResponsesToOpenAIRequest } =
+ await import("../../open-sse/translator/request/openai-responses.ts");
+
+const { XaiExecutor } = await import("../../open-sse/executors/xai.ts");
+
+// ── Predicate ──────────────────────────────────────────────────────────────
+
+test("native xAI passthrough: true for xai-oauth + responses endpoint", () => {
+ assert.equal(
+ shouldUseNativeXaiResponsesPassthrough({
+ provider: "xai-oauth",
+ sourceFormat: "openai-responses",
+ endpointPath: "/v1/responses",
+ }),
+ true
+ );
+});
+
+test("native xAI passthrough: true for xao alias and xai API key provider", () => {
+ assert.equal(
+ shouldUseNativeXaiResponsesPassthrough({
+ provider: "xao",
+ sourceFormat: "openai-responses",
+ endpointPath: "/v1/responses",
+ }),
+ true
+ );
+ assert.equal(
+ shouldUseNativeXaiResponsesPassthrough({
+ provider: "xai",
+ sourceFormat: "openai-responses",
+ endpointPath: "responses",
+ }),
+ true
+ );
+});
+
+test("native xAI passthrough: false for grok-cli / chat completions / wrong format", () => {
+ assert.equal(
+ shouldUseNativeXaiResponsesPassthrough({
+ provider: "grok-cli",
+ sourceFormat: "openai-responses",
+ endpointPath: "/v1/responses",
+ }),
+ false
+ );
+ assert.equal(
+ shouldUseNativeXaiResponsesPassthrough({
+ provider: "xai-oauth",
+ sourceFormat: "openai-responses",
+ endpointPath: "/v1/chat/completions",
+ }),
+ false
+ );
+ assert.equal(
+ shouldUseNativeXaiResponsesPassthrough({
+ provider: "xai-oauth",
+ sourceFormat: "openai",
+ endpointPath: "/v1/responses",
+ }),
+ false
+ );
+ assert.ok(XAI_API_PROVIDERS.has("xai-oauth"));
+ assert.equal(XAI_API_PROVIDERS.has("grok-cli"), false);
+});
+
+test("Codex passthrough is unchanged and does not match xAI providers", () => {
+ assert.equal(
+ shouldUseNativeCodexPassthrough({
+ provider: "xai-oauth",
+ sourceFormat: "openai-responses",
+ endpointPath: "/v1/responses",
+ }),
+ false
+ );
+ assert.equal(
+ shouldUseNativeCodexPassthrough({
+ provider: "codex",
+ sourceFormat: "openai-responses",
+ endpointPath: "/v1/responses",
+ }),
+ true
+ );
+});
+
+test("isResponsesEndpointPath shared helper", () => {
+ assert.equal(isResponsesEndpointPath("/v1/responses"), true);
+ assert.equal(isResponsesEndpointPath("/v1/responses/"), true);
+ assert.equal(isResponsesEndpointPath("responses"), true);
+ assert.equal(isResponsesEndpointPath("/v1/chat/completions"), false);
+});
+
+test("stampNativeResponsesPassthroughBody modes", () => {
+ assert.deepEqual(stampNativeResponsesPassthroughBody({ a: 1 }, "codex"), {
+ a: 1,
+ _nativeCodexPassthrough: true,
+ });
+ assert.deepEqual(stampNativeResponsesPassthroughBody({ a: 1 }, "xai"), {
+ a: 1,
+ _nativeXaiResponsesPassthrough: true,
+ });
+});
+
+// ── Web search rewrite bypass (via existing nativeCodexPassthrough flag) ──
+
+test("web_search is NOT rewritten when native Responses passthrough flag is true", () => {
+ const input = { tools: [{ type: "web_search" }, { type: "x_search", from_date: "2026-05-01" }] };
+ const { body, fallback } = prepareWebSearchFallbackBody(input, {
+ provider: "xai-oauth",
+ sourceFormat: "openai-responses",
+ targetFormat: "openai-responses",
+ nativeCodexPassthrough: true,
+ });
+ assert.equal(fallback.enabled, false);
+ assert.deepEqual(body, input);
+});
+
+test("web_search IS rewritten for xAI when native passthrough flag is false", () => {
+ const { body, fallback } = prepareWebSearchFallbackBody(
+ { tools: [{ type: "web_search" }] },
+ {
+ provider: "xai-oauth",
+ sourceFormat: "openai-responses",
+ targetFormat: "openai",
+ nativeCodexPassthrough: false,
+ }
+ );
+ assert.equal(fallback.enabled, true);
+ assert.equal(fallback.toolName, OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME);
+ const tools = body.tools as Record[];
+ const names = tools.map((t) => (t.function ? (t.function as { name?: string }).name : t.name));
+ assert.ok(names.includes(OMNIROUTE_WEB_SEARCH_FALLBACK_TOOL_NAME));
+});
+
+test("interceptSearchOverride true still forces rewrite on passthrough path", () => {
+ assert.equal(
+ supportsNativeWebSearchFallbackBypass({
+ provider: "xai-oauth",
+ sourceFormat: "openai-responses",
+ targetFormat: "openai-responses",
+ nativeCodexPassthrough: true,
+ interceptSearchOverride: true,
+ }),
+ false
+ );
+});
+
+// ── Translator allowlist / Chat drop ───────────────────────────────────────
+
+test("Responses→Chat validation accepts x_search (no unsupported_feature 400)", () => {
+ assert.doesNotThrow(() => {
+ openaiResponsesToOpenAIRequest(
+ "gpt-4o",
+ {
+ model: "gpt-4o",
+ input: "hi",
+ tools: [
+ { type: "web_search" },
+ { type: "x_search", from_date: "2026-05-01", to_date: "2026-08-01" },
+ {
+ type: "function",
+ name: "lookup",
+ parameters: { type: "object", properties: {} },
+ },
+ ],
+ },
+ false,
+ {}
+ );
+ });
+});
+
+test("Responses→Chat conversion drops x_search (no Chat equivalent)", () => {
+ const converted = openaiResponsesToOpenAIRequest(
+ "gpt-4o",
+ {
+ model: "gpt-4o",
+ input: "hi",
+ tools: [
+ { type: "x_search", from_date: "2026-05-01" },
+ {
+ type: "function",
+ name: "lookup",
+ parameters: { type: "object", properties: {} },
+ },
+ ],
+ },
+ false,
+ {}
+ ) as { tools?: Array<{ type?: string; function?: { name?: string }; name?: string }> };
+
+ const tools = converted.tools || [];
+ assert.equal(
+ tools.some((t) => t.type === "x_search"),
+ false,
+ "x_search must not be forwarded on Chat Completions downgrade"
+ );
+ assert.ok(
+ tools.some((t) => t.function?.name === "lookup" || t.name === "lookup"),
+ "function tools survive"
+ );
+});
+
+// ── XaiExecutor URL + marker strip ─────────────────────────────────────────
+
+test("XaiExecutor.buildUrl uses responsesBaseUrl when requestEndpointPath is /v1/responses", () => {
+ const executor = new XaiExecutor("xai-oauth");
+ const chatUrl = String(executor.buildUrl("grok-4.20-0309-reasoning", false, 0, null));
+ assert.ok(
+ chatUrl.includes("chat/completions") || !chatUrl.includes("/responses"),
+ `default chat path expected, got ${chatUrl}`
+ );
+
+ const responsesUrl = String(
+ executor.buildUrl("grok-4.20-0309-reasoning", false, 0, {
+ requestEndpointPath: "/v1/responses",
+ } as never)
+ );
+ assert.match(responsesUrl, /\/responses/);
+});
+
+test("XaiExecutor.transformRequest strips internal passthrough markers", () => {
+ const executor = new XaiExecutor("xai-oauth");
+ const out = executor.transformRequest(
+ "grok-4.20-0309-reasoning",
+ {
+ model: "grok-4.20-0309-reasoning",
+ input: "hi",
+ tools: [{ type: "web_search" }, { type: "x_search" }],
+ _nativeXaiResponsesPassthrough: true,
+ _nativeCodexPassthrough: true,
+ },
+ false,
+ {} as never
+ ) as Record;
+
+ assert.equal(out._nativeXaiResponsesPassthrough, undefined);
+ assert.equal(out._nativeCodexPassthrough, undefined);
+ assert.ok(Array.isArray(out.tools));
+ assert.equal((out.tools as unknown[]).length, 2);
+});
+
+// ── Composition invariant ──────────────────────────────────────────────────
+
+test("composition: flag → targetFormat Responses → no rewrite → credentials stamp → Responses URL", async () => {
+ const { resolveChatCoreRequestFormat } =
+ await import("../../open-sse/handlers/chatCore/requestFormat.ts");
+ const { resolveChatCoreTargetFormat } =
+ await import("../../open-sse/handlers/chatCore/targetFormat.ts");
+ const { resolveExecutionCredentials } =
+ await import("../../open-sse/handlers/chatCore/executionCredentials.ts");
+
+ const fmt = resolveChatCoreRequestFormat({
+ body: {
+ input: "hi",
+ tools: [{ type: "web_search" }, { type: "x_search", from_date: "2026-05-01" }],
+ },
+ provider: "xai-oauth",
+ userAgent: "unit-test",
+ clientRawRequest: { endpoint: "/v1/responses", headers: new Headers() },
+ });
+ assert.equal(fmt.nativeXaiResponsesPassthrough, true);
+ const nativeResponsesPassthrough =
+ fmt.nativeCodexPassthrough || fmt.nativeXaiResponsesPassthrough;
+
+ const { targetFormat } = resolveChatCoreTargetFormat({
+ provider: "xai-oauth",
+ resolvedModel: "grok-4.20-0309-reasoning",
+ apiFormat: undefined,
+ customModelTargetFormat: undefined,
+ providerSpecificData: undefined,
+ nativeXaiResponsesPassthrough: fmt.nativeXaiResponsesPassthrough,
+ });
+ assert.equal(targetFormat, "openai-responses");
+
+ const { fallback } = prepareWebSearchFallbackBody(
+ { tools: [{ type: "web_search" }, { type: "x_search" }] },
+ {
+ provider: "xai-oauth",
+ sourceFormat: fmt.sourceFormat,
+ targetFormat,
+ nativeCodexPassthrough: nativeResponsesPassthrough,
+ }
+ );
+ assert.equal(fallback.enabled, false);
+
+ const creds = resolveExecutionCredentials({
+ credentials: {},
+ nativeCodexPassthrough: nativeResponsesPassthrough,
+ endpointPath: fmt.endpointPath,
+ targetFormat,
+ provider: "xai-oauth",
+ ccSessionId: null,
+ }) as { requestEndpointPath?: string };
+ assert.equal(creds.requestEndpointPath, "/v1/responses");
+
+ const url = String(
+ new XaiExecutor("xai-oauth").buildUrl("grok-4.20-0309-reasoning", false, 0, {
+ requestEndpointPath: creds.requestEndpointPath,
+ } as never)
+ );
+ assert.match(url, /\/responses/);
+ assert.equal(url.includes("chat/completions"), false);
+});
+
+// ── Response fidelity ──────────────────────────────────────────────────────
+
+test("sanitizeResponsesApiResponse keeps xAI server-side tool usage + cost ticks", async () => {
+ const { sanitizeResponsesApiResponse } =
+ await import("../../open-sse/handlers/responseSanitizer.ts");
+ const sanitized = sanitizeResponsesApiResponse({
+ id: "resp_test",
+ object: "response",
+ created_at: 1,
+ model: "grok-4.20-0309-reasoning",
+ status: "completed",
+ output: [
+ {
+ id: "ws_1",
+ type: "web_search_call",
+ status: "completed",
+ action: { type: "search", query: "stripe" },
+ },
+ {
+ id: "msg_1",
+ type: "message",
+ role: "assistant",
+ content: [{ type: "output_text", text: '{"ok":true}' }],
+ },
+ ],
+ usage: {
+ input_tokens: 10,
+ output_tokens: 5,
+ total_tokens: 15,
+ cost_in_usd_ticks: 154733500,
+ server_side_tool_usage_details: { web_search_calls: 1, x_search_calls: 0 },
+ },
+ cost_in_usd_ticks: 154733500,
+ server_side_tool_usage_details: { web_search_calls: 1 },
+ }) as Record;
+
+ const usage = sanitized.usage as Record;
+ assert.equal(usage.cost_in_usd_ticks, 154733500);
+ assert.deepEqual(usage.server_side_tool_usage_details, {
+ web_search_calls: 1,
+ x_search_calls: 0,
+ });
+ assert.equal(sanitized.cost_in_usd_ticks, 154733500);
+ assert.deepEqual(sanitized.server_side_tool_usage_details, { web_search_calls: 1 });
+ assert.ok((sanitized.output as { type: string }[]).some((o) => o.type === "web_search_call"));
+});
+
+test("filterUsageForFormat(Responses) keeps cost ticks + server-side tool details", async () => {
+ const { filterUsageForFormat } = await import("../../open-sse/utils/usageTracking.ts");
+ const filtered = filterUsageForFormat(
+ {
+ input_tokens: 10,
+ output_tokens: 5,
+ total_tokens: 15,
+ cost_in_usd_ticks: 154733500,
+ server_side_tool_usage_details: { web_search_calls: 1, x_search_calls: 0 },
+ server_side_tool_usage: { web_search: 1 },
+ x_groq: { should_not: "survive" },
+ },
+ "openai-responses"
+ ) as Record;
+
+ assert.equal(filtered.cost_in_usd_ticks, 154733500);
+ assert.deepEqual(filtered.server_side_tool_usage_details, {
+ web_search_calls: 1,
+ x_search_calls: 0,
+ });
+ assert.equal(filtered.x_groq, undefined);
+});