mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
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:
committed by
GitHub
parent
6530948812
commit
68ba1ef641
@@ -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
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user