refactor(xai): share responses tool request transport (#128571)

* refactor(xai): share responses tool request transport

* refactor(xai): remove unused responses body test seam

* test(xai): preserve responses request contracts at fetch boundaries
This commit is contained in:
Peter Steinberger
2026-08-24 00:57:28 -07:00
committed by GitHub
parent 6530948812
commit 68ba1ef641
8 changed files with 71 additions and 114 deletions
+2 -3
View File
@@ -1457,12 +1457,11 @@ extensions/xai/realtime-voice-config.ts 2
extensions/xai/runtime-model-compat.ts 1
extensions/xai/speech-provider-metadata.ts 3
extensions/xai/src/code-execution-config.ts 10
extensions/xai/src/code-execution-shared.ts 1
extensions/xai/src/tool-auth-shared.ts 2
extensions/xai/src/web-search-provider.runtime.ts 2
extensions/xai/src/web-search-shared.ts 2
extensions/xai/src/web-search-shared.ts 1
extensions/xai/src/x-search-config.ts 4
extensions/xai/src/x-search-shared.ts 2
extensions/xai/src/x-search-shared.ts 1
extensions/xai/stream.ts 5
extensions/xai/tts.ts 1
extensions/xai/x-search.ts 7
+10 -1
View File
@@ -219,7 +219,7 @@ describe("xai code_execution tool", () => {
});
});
it("reuses the xAI plugin web search key for code_execution requests", async () => {
it("reuses the xAI plugin web search key without overriding custom model reasoning", async () => {
const mockFetch = installCodeExecutionFetch();
const tool = createCodeExecutionTool({
config: {
@@ -230,6 +230,7 @@ describe("xai code_execution tool", () => {
webSearch: {
apiKey: "xai-plugin-key", // pragma: allowlist secret
},
codeExecution: { model: "grok-build-0.1" },
},
},
},
@@ -242,6 +243,14 @@ describe("xai code_execution tool", () => {
});
expect(firstAuthorizationHeader(mockFetch)).toBe("Bearer xai-plugin-key");
const body = parseFirstRequestBody(mockFetch);
expect(body.model).toBe("grok-build-0.1");
expect(body.input).toEqual([
{ role: "user", content: "Compute the standard deviation of [1, 2, 3]" },
]);
expect(body.store).toBe(false);
expect(body).not.toHaveProperty("reasoning");
expect(body).not.toHaveProperty("max_turns");
});
it("reports malformed code_execution JSON as a provider error", async () => {
+9 -27
View File
@@ -1,9 +1,7 @@
// Xai plugin module implements code execution shared behavior.
import { readProviderJsonObjectResponse } from "openclaw/plugin-sdk/provider-http";
import { postTrustedWebToolsJson } from "openclaw/plugin-sdk/provider-web-search";
import { XAI_DEFAULT_MODEL_ID } from "../model-definitions.js";
import {
buildXaiResponsesToolBody,
requestXaiResponsesTool,
requireXaiResponseTextAndCitations,
XAI_RESPONSES_ENDPOINT,
} from "./responses-tool-shared.js";
@@ -11,17 +9,10 @@ import {
resolveNormalizedXaiToolModel,
resolvePositiveIntegerToolConfig,
} from "./tool-config-shared.js";
import type { XaiWebSearchResponse } from "./web-search-shared.js";
const XAI_CODE_EXECUTION_ENDPOINT = XAI_RESPONSES_ENDPOINT;
const XAI_DEFAULT_CODE_EXECUTION_MODEL = XAI_DEFAULT_MODEL_ID;
type XaiCodeExecutionResponse = XaiWebSearchResponse & {
output?: Array<{
type?: string;
}>;
};
type XaiCodeExecutionResult = {
content: string;
citations: string[];
@@ -70,25 +61,16 @@ export async function requestXaiCodeExecution(params: {
maxTurns?: number;
task: string;
}): Promise<XaiCodeExecutionResult> {
return await postTrustedWebToolsJson(
return await requestXaiResponsesTool(
{
url: XAI_CODE_EXECUTION_ENDPOINT,
timeoutSeconds: params.timeoutSeconds,
apiKey: params.apiKey,
body: buildXaiResponsesToolBody({
model: params.model,
inputText: params.task,
tools: [{ type: "code_interpreter" }],
maxTurns: params.maxTurns,
reasoningEffort: params.model === XAI_DEFAULT_CODE_EXECUTION_MODEL ? "low" : undefined,
}),
errorLabel: "xAI",
...params,
endpoint: XAI_CODE_EXECUTION_ENDPOINT,
inputText: params.task,
tools: [{ type: "code_interpreter" }],
reasoningEffort: params.model === XAI_DEFAULT_CODE_EXECUTION_MODEL ? "low" : undefined,
errorLabel: "xAI code execution failed",
},
async (response) => {
const data = (await readProviderJsonObjectResponse(
response,
"xAI code execution failed",
)) as XaiCodeExecutionResponse;
(data) => {
const { content, citations } = requireXaiResponseTextAndCitations(
data,
"xAI code execution failed",
@@ -1,47 +1,12 @@
// Xai tests cover responses tool shared plugin behavior.
import { describe, expect, it } from "vitest";
import {
buildXaiResponsesToolBody,
extractXaiWebSearchContent,
requireXaiResponseTextAndCitations,
requireXaiResponseTextCitationsAndInline,
} from "./responses-tool-shared.js";
describe("xai responses tool helpers", () => {
it("builds the shared xAI Responses tool body", () => {
expect(
buildXaiResponsesToolBody({
model: "grok-4.3",
inputText: "search for openclaw",
tools: [{ type: "x_search" }],
maxTurns: 2,
reasoningEffort: "none",
}),
).toEqual({
model: "grok-4.3",
input: [{ role: "user", content: "search for openclaw" }],
tools: [{ type: "x_search" }],
store: false,
reasoning: { effort: "none" },
max_turns: 2,
});
});
it("keeps custom model reasoning untouched while disabling response storage", () => {
expect(
buildXaiResponsesToolBody({
model: "grok-build-0.1",
inputText: "run code",
tools: [{ type: "code_interpreter" }],
}),
).toEqual({
model: "grok-build-0.1",
input: [{ role: "user", content: "run code" }],
tools: [{ type: "code_interpreter" }],
store: false,
});
});
it("falls back to annotation citations when the API omits top-level citations", () => {
expect(
requireXaiResponseTextAndCitations(
+27 -1
View File
@@ -1,4 +1,6 @@
// Xai plugin module implements responses tool shared behavior.
import { readProviderJsonObjectResponse } from "openclaw/plugin-sdk/provider-http";
import { postTrustedWebToolsJson } from "openclaw/plugin-sdk/provider-web-search";
import { truncateSanitizedExternalContent } from "openclaw/plugin-sdk/security-runtime";
import {
isRecord,
@@ -55,7 +57,7 @@ export function resolveXaiResponsesEndpoint(baseUrl?: unknown): string {
return `${(trimString(baseUrl) ?? XAI_RESPONSES_BASE_URL).replace(/\/+$/, "")}/responses`;
}
export function buildXaiResponsesToolBody(params: {
function buildXaiResponsesToolBody(params: {
model: string;
inputText: string;
tools: Array<Record<string, unknown>>;
@@ -72,6 +74,30 @@ export function buildXaiResponsesToolBody(params: {
};
}
export async function requestXaiResponsesTool<T>(
params: Parameters<typeof buildXaiResponsesToolBody>[0] & {
apiKey: string;
endpoint: string;
timeoutSeconds: number;
errorLabel: string;
signal?: AbortSignal;
},
parseResponse: (data: XaiWebSearchResponse) => T,
): Promise<T> {
return await postTrustedWebToolsJson(
{
url: params.endpoint,
timeoutSeconds: params.timeoutSeconds,
apiKey: params.apiKey,
...(params.signal ? { signal: params.signal } : {}),
body: buildXaiResponsesToolBody(params),
errorLabel: "xAI",
},
async (response) =>
parseResponse(await readProviderJsonObjectResponse(response, params.errorLabel)),
);
}
export function extractXaiWebSearchContent(
data: XaiWebSearchResponse,
maxContentChars?: number,
+11 -23
View File
@@ -1,11 +1,10 @@
// Xai plugin module implements web search shared behavior.
import { readProviderJsonObjectResponse } from "openclaw/plugin-sdk/provider-http";
import { postTrustedWebToolsJson, wrapWebContent } from "openclaw/plugin-sdk/provider-web-search";
import { wrapWebContent } from "openclaw/plugin-sdk/provider-web-search";
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
import { XAI_DEFAULT_MODEL_ID } from "../model-definitions.js";
import { normalizeXaiModelId } from "../model-id.js";
import {
buildXaiResponsesToolBody,
requestXaiResponsesTool,
requireXaiResponseTextCitationsAndInline,
resolveXaiResponsesEndpoint,
} from "./responses-tool-shared.js";
@@ -108,32 +107,21 @@ export async function requestXaiWebSearch(params: {
signal?: AbortSignal;
}): Promise<XaiWebSearchResult> {
params.signal?.throwIfAborted();
return await postTrustedWebToolsJson(
return await requestXaiResponsesTool(
{
url: params.endpoint,
timeoutSeconds: params.timeoutSeconds,
apiKey: params.apiKey,
...(params.signal ? { signal: params.signal } : {}),
body: buildXaiResponsesToolBody({
model: params.model,
inputText: params.query,
tools: [{ type: "web_search" }],
reasoningEffort: params.model === XAI_DEFAULT_WEB_SEARCH_MODEL ? "low" : undefined,
}),
errorLabel: "xAI",
...params,
inputText: params.query,
tools: [{ type: "web_search" }],
reasoningEffort: params.model === XAI_DEFAULT_WEB_SEARCH_MODEL ? "low" : undefined,
errorLabel: "xAI web search failed",
},
async (response) => {
const data = (await readProviderJsonObjectResponse(
response,
"xAI web search failed",
)) as XaiWebSearchResponse;
return requireXaiResponseTextCitationsAndInline(
(data) =>
requireXaiResponseTextCitationsAndInline(
data,
"xAI web search failed",
params.inlineCitations,
XAI_WEB_SEARCH_MAX_CONTENT_CHARS,
);
},
),
).catch((error: unknown) => {
if (params.signal?.aborted && error === params.signal.reason) {
throw error;
+11 -24
View File
@@ -1,9 +1,8 @@
// Xai plugin module implements x search shared behavior.
import { readProviderJsonObjectResponse } from "openclaw/plugin-sdk/provider-http";
import { postTrustedWebToolsJson, wrapWebContent } from "openclaw/plugin-sdk/provider-web-search";
import { wrapWebContent } from "openclaw/plugin-sdk/provider-web-search";
import { XAI_DEFAULT_MODEL_ID } from "../model-definitions.js";
import {
buildXaiResponsesToolBody,
requestXaiResponsesTool,
requireXaiResponseTextCitationsAndInline,
resolveXaiResponsesEndpoint,
} from "./responses-tool-shared.js";
@@ -126,32 +125,20 @@ export async function requestXaiXSearch(params: {
signal?: AbortSignal;
}): Promise<XaiXSearchResult> {
params.signal?.throwIfAborted();
return await postTrustedWebToolsJson(
return await requestXaiResponsesTool(
{
url: params.endpoint,
timeoutSeconds: params.timeoutSeconds,
apiKey: params.apiKey,
...(params.signal ? { signal: params.signal } : {}),
body: buildXaiResponsesToolBody({
model: params.model,
inputText: params.options.query,
tools: [buildXSearchTool(params.options)],
maxTurns: params.maxTurns,
reasoningEffort: params.model === XAI_DEFAULT_X_SEARCH_MODEL ? "none" : undefined,
}),
errorLabel: "xAI",
...params,
inputText: params.options.query,
tools: [buildXSearchTool(params.options)],
reasoningEffort: params.model === XAI_DEFAULT_X_SEARCH_MODEL ? "none" : undefined,
errorLabel: "xAI X search failed",
},
async (response) => {
const data = (await readProviderJsonObjectResponse(
response,
"xAI X search failed",
)) as XaiWebSearchResponse;
return requireXaiResponseTextCitationsAndInline(
(data) =>
requireXaiResponseTextCitationsAndInline(
data,
"xAI X search failed",
params.inlineCitations,
XAI_X_SEARCH_MAX_CONTENT_CHARS,
);
},
),
);
}
+1
View File
@@ -315,6 +315,7 @@ describe("xai x_search tool", () => {
expect(firstFetchUrl(mockFetch)).toContain("api.x.ai/v1/responses");
const body = parseFirstRequestBody(mockFetch);
expect(body.model).toBe("grok-4.3");
expect(body.input).toEqual([{ role: "user", content: "dinner recipes" }]);
expect(body.store).toBe(false);
expect(body.reasoning).toEqual({ effort: "none" });
expect(body.max_turns).toBe(2);