mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix(xai): wrap malformed tool json
This commit is contained in:
@@ -83,6 +83,7 @@ Docs: https://docs.openclaw.ai
|
||||
- ComfyUI: report malformed workflow API JSON responses with owned errors instead of leaking raw parser failures.
|
||||
- DeepInfra video: report malformed successful API JSON responses with provider-owned errors instead of leaking raw parser failures.
|
||||
- Brave Search: report malformed web and LLM-context API JSON with provider-owned errors instead of leaking raw parser failures.
|
||||
- xAI tools: report malformed web search, X search, and code execution JSON with provider-owned errors instead of leaking raw parser failures.
|
||||
- Twilio voice-call: report malformed successful API JSON responses with provider-owned errors instead of leaking raw parser failures.
|
||||
- Voice-call provider APIs: report malformed successful guarded JSON responses with provider-prefixed errors instead of leaking raw parser failures.
|
||||
- Realtime transcription: report malformed provider websocket JSON frames with owned parser errors instead of leaking raw `SyntaxError` objects.
|
||||
|
||||
@@ -175,6 +175,37 @@ describe("xai code_execution tool", () => {
|
||||
expect(firstAuthorizationHeader(mockFetch)).toBe("Bearer xai-plugin-key");
|
||||
});
|
||||
|
||||
it("reports malformed code_execution JSON as a provider error", async () => {
|
||||
const mockFetch = vi.fn((_input?: unknown, _init?: unknown) =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.reject(new SyntaxError("Unexpected token")),
|
||||
} as Response),
|
||||
);
|
||||
global.fetch = withFetchPreconnect(mockFetch);
|
||||
const tool = createCodeExecutionTool({
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
xai: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "xai-plugin-key", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
tool?.execute?.("code-execution:malformed-json", {
|
||||
task: "Calculate the mean of [40, 42, 44]",
|
||||
}),
|
||||
).rejects.toThrow("xAI code execution failed: malformed JSON response");
|
||||
});
|
||||
|
||||
it("reuses the legacy grok web search key for code_execution requests", async () => {
|
||||
const mockFetch = installCodeExecutionFetch();
|
||||
const tool = createCodeExecutionTool({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
||||
import { postTrustedWebToolsJson } from "openclaw/plugin-sdk/provider-web-search";
|
||||
import {
|
||||
buildXaiResponsesToolBody,
|
||||
@@ -81,7 +82,10 @@ export async function requestXaiCodeExecution(params: {
|
||||
errorLabel: "xAI",
|
||||
},
|
||||
async (response) => {
|
||||
const data = (await response.json()) as XaiCodeExecutionResponse;
|
||||
const data = await readProviderJsonResponse<XaiCodeExecutionResponse>(
|
||||
response,
|
||||
"xAI code execution failed",
|
||||
);
|
||||
const { content, citations } = resolveXaiResponseTextAndCitations(data);
|
||||
const outputTypes = Array.isArray(data.output)
|
||||
? [
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
||||
import { postTrustedWebToolsJson, wrapWebContent } from "openclaw/plugin-sdk/provider-web-search";
|
||||
import { normalizeXaiModelId } from "../model-id.js";
|
||||
import {
|
||||
@@ -109,7 +110,10 @@ export async function requestXaiWebSearch(params: {
|
||||
errorLabel: "xAI",
|
||||
},
|
||||
async (response) => {
|
||||
const data = (await response.json()) as XaiWebSearchResponse;
|
||||
const data = await readProviderJsonResponse<XaiWebSearchResponse>(
|
||||
response,
|
||||
"xAI web search failed",
|
||||
);
|
||||
return resolveXaiResponseTextCitationsAndInline(data, params.inlineCitations);
|
||||
},
|
||||
).catch((error: unknown) => wrapXaiWebSearchError(error, params.timeoutSeconds));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
||||
import { postTrustedWebToolsJson, wrapWebContent } from "openclaw/plugin-sdk/provider-web-search";
|
||||
import {
|
||||
buildXaiResponsesToolBody,
|
||||
@@ -131,7 +132,10 @@ export async function requestXaiXSearch(params: {
|
||||
errorLabel: "xAI",
|
||||
},
|
||||
async (response) => {
|
||||
const data = (await response.json()) as XaiWebSearchResponse;
|
||||
const data = await readProviderJsonResponse<XaiWebSearchResponse>(
|
||||
response,
|
||||
"xAI X search failed",
|
||||
);
|
||||
return resolveXaiResponseTextCitationsAndInline(data, params.inlineCitations);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -355,6 +355,36 @@ describe("xai web search config resolution", () => {
|
||||
expect(firstFetchUrl(mockFetch)).toBe("https://api.x.ai/proxy/v1/responses");
|
||||
});
|
||||
|
||||
it("reports malformed xAI web search JSON as a provider error", async () => {
|
||||
const mockFetch = vi.fn((_input?: unknown, _init?: unknown) =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.reject(new SyntaxError("Unexpected token")),
|
||||
} as Response),
|
||||
);
|
||||
global.fetch = withFetchPreconnect(mockFetch);
|
||||
const provider = createXaiWebSearchProvider();
|
||||
const tool = provider.createTool({
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
xai: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "xai-test-key", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(tool.execute({ query: "OpenClaw" })).rejects.toThrow(
|
||||
"xAI web search failed: malformed JSON response",
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes deprecated grok 4.20 beta model ids to GA ids", () => {
|
||||
expect(
|
||||
resolveXaiWebSearchModel({
|
||||
|
||||
@@ -290,6 +290,40 @@ describe("xai x_search tool", () => {
|
||||
expect(firstAuthorizationHeader(mockFetch)).toBe("Bearer xai-plugin-key");
|
||||
});
|
||||
|
||||
it("reports malformed x_search JSON as a provider error", async () => {
|
||||
const mockFetch = vi.fn((_input?: unknown, _init?: unknown) =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.reject(new SyntaxError("Unexpected token")),
|
||||
} as Response),
|
||||
);
|
||||
global.fetch = withFetchPreconnect(mockFetch);
|
||||
const tool = createXSearchTool({
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
xai: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "xai-plugin-key", // pragma: allowlist secret
|
||||
},
|
||||
xSearch: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
tool?.execute?.("x-search:malformed-json", {
|
||||
query: "latest post from huntharo",
|
||||
}),
|
||||
).rejects.toThrow("xAI X search failed: malformed JSON response");
|
||||
});
|
||||
|
||||
it("prefers the active runtime config for shared xAI keys", async () => {
|
||||
const mockFetch = installXSearchFetch();
|
||||
const tool = createXSearchTool({
|
||||
|
||||
Reference in New Issue
Block a user