fix(xai): wrap malformed tool json · openclaw/openclaw@ffae8f3
vincentkoc
·
2026-05-15
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -83,6 +83,7 @@ Docs: https://docs.openclaw.ai
|
83 | 83 | - ComfyUI: report malformed workflow API JSON responses with owned errors instead of leaking raw parser failures. |
84 | 84 | - DeepInfra video: report malformed successful API JSON responses with provider-owned errors instead of leaking raw parser failures. |
85 | 85 | - Brave Search: report malformed web and LLM-context API JSON with provider-owned errors instead of leaking raw parser failures. |
| 86 | +- xAI tools: report malformed web search, X search, and code execution JSON with provider-owned errors instead of leaking raw parser failures. |
86 | 87 | - Twilio voice-call: report malformed successful API JSON responses with provider-owned errors instead of leaking raw parser failures. |
87 | 88 | - Voice-call provider APIs: report malformed successful guarded JSON responses with provider-prefixed errors instead of leaking raw parser failures. |
88 | 89 | - Realtime transcription: report malformed provider websocket JSON frames with owned parser errors instead of leaking raw `SyntaxError` objects. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -175,6 +175,37 @@ describe("xai code_execution tool", () => {
|
175 | 175 | expect(firstAuthorizationHeader(mockFetch)).toBe("Bearer xai-plugin-key"); |
176 | 176 | }); |
177 | 177 | |
| 178 | +it("reports malformed code_execution JSON as a provider error", async () => { |
| 179 | +const mockFetch = vi.fn((_input?: unknown, _init?: unknown) => |
| 180 | +Promise.resolve({ |
| 181 | +ok: true, |
| 182 | +json: () => Promise.reject(new SyntaxError("Unexpected token")), |
| 183 | +} as Response), |
| 184 | +); |
| 185 | +global.fetch = withFetchPreconnect(mockFetch); |
| 186 | +const tool = createCodeExecutionTool({ |
| 187 | +config: { |
| 188 | +plugins: { |
| 189 | +entries: { |
| 190 | +xai: { |
| 191 | +config: { |
| 192 | +webSearch: { |
| 193 | +apiKey: "xai-plugin-key", // pragma: allowlist secret |
| 194 | +}, |
| 195 | +}, |
| 196 | +}, |
| 197 | +}, |
| 198 | +}, |
| 199 | +}, |
| 200 | +}); |
| 201 | + |
| 202 | +await expect( |
| 203 | +tool?.execute?.("code-execution:malformed-json", { |
| 204 | +task: "Calculate the mean of [40, 42, 44]", |
| 205 | +}), |
| 206 | +).rejects.toThrow("xAI code execution failed: malformed JSON response"); |
| 207 | +}); |
| 208 | + |
178 | 209 | it("reuses the legacy grok web search key for code_execution requests", async () => { |
179 | 210 | const mockFetch = installCodeExecutionFetch(); |
180 | 211 | const tool = createCodeExecutionTool({ |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http"; |
1 | 2 | import { postTrustedWebToolsJson } from "openclaw/plugin-sdk/provider-web-search"; |
2 | 3 | import { |
3 | 4 | buildXaiResponsesToolBody, |
@@ -81,7 +82,10 @@ export async function requestXaiCodeExecution(params: {
|
81 | 82 | errorLabel: "xAI", |
82 | 83 | }, |
83 | 84 | async (response) => { |
84 | | -const data = (await response.json()) as XaiCodeExecutionResponse; |
| 85 | +const data = await readProviderJsonResponse<XaiCodeExecutionResponse>( |
| 86 | +response, |
| 87 | +"xAI code execution failed", |
| 88 | +); |
85 | 89 | const { content, citations } = resolveXaiResponseTextAndCitations(data); |
86 | 90 | const outputTypes = Array.isArray(data.output) |
87 | 91 | ? [ |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http"; |
1 | 2 | import { postTrustedWebToolsJson, wrapWebContent } from "openclaw/plugin-sdk/provider-web-search"; |
2 | 3 | import { normalizeXaiModelId } from "../model-id.js"; |
3 | 4 | import { |
@@ -109,7 +110,10 @@ export async function requestXaiWebSearch(params: {
|
109 | 110 | errorLabel: "xAI", |
110 | 111 | }, |
111 | 112 | async (response) => { |
112 | | -const data = (await response.json()) as XaiWebSearchResponse; |
| 113 | +const data = await readProviderJsonResponse<XaiWebSearchResponse>( |
| 114 | +response, |
| 115 | +"xAI web search failed", |
| 116 | +); |
113 | 117 | return resolveXaiResponseTextCitationsAndInline(data, params.inlineCitations); |
114 | 118 | }, |
115 | 119 | ).catch((error: unknown) => wrapXaiWebSearchError(error, params.timeoutSeconds)); |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http"; |
1 | 2 | import { postTrustedWebToolsJson, wrapWebContent } from "openclaw/plugin-sdk/provider-web-search"; |
2 | 3 | import { |
3 | 4 | buildXaiResponsesToolBody, |
@@ -131,7 +132,10 @@ export async function requestXaiXSearch(params: {
|
131 | 132 | errorLabel: "xAI", |
132 | 133 | }, |
133 | 134 | async (response) => { |
134 | | -const data = (await response.json()) as XaiWebSearchResponse; |
| 135 | +const data = await readProviderJsonResponse<XaiWebSearchResponse>( |
| 136 | +response, |
| 137 | +"xAI X search failed", |
| 138 | +); |
135 | 139 | return resolveXaiResponseTextCitationsAndInline(data, params.inlineCitations); |
136 | 140 | }, |
137 | 141 | ); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -355,6 +355,36 @@ describe("xai web search config resolution", () => {
|
355 | 355 | expect(firstFetchUrl(mockFetch)).toBe("https://api.x.ai/proxy/v1/responses"); |
356 | 356 | }); |
357 | 357 | |
| 358 | +it("reports malformed xAI web search JSON as a provider error", async () => { |
| 359 | +const mockFetch = vi.fn((_input?: unknown, _init?: unknown) => |
| 360 | +Promise.resolve({ |
| 361 | +ok: true, |
| 362 | +json: () => Promise.reject(new SyntaxError("Unexpected token")), |
| 363 | +} as Response), |
| 364 | +); |
| 365 | +global.fetch = withFetchPreconnect(mockFetch); |
| 366 | +const provider = createXaiWebSearchProvider(); |
| 367 | +const tool = provider.createTool({ |
| 368 | +config: { |
| 369 | +plugins: { |
| 370 | +entries: { |
| 371 | +xai: { |
| 372 | +config: { |
| 373 | +webSearch: { |
| 374 | +apiKey: "xai-test-key", // pragma: allowlist secret |
| 375 | +}, |
| 376 | +}, |
| 377 | +}, |
| 378 | +}, |
| 379 | +}, |
| 380 | +}, |
| 381 | +}); |
| 382 | + |
| 383 | +await expect(tool.execute({ query: "OpenClaw" })).rejects.toThrow( |
| 384 | +"xAI web search failed: malformed JSON response", |
| 385 | +); |
| 386 | +}); |
| 387 | + |
358 | 388 | it("normalizes deprecated grok 4.20 beta model ids to GA ids", () => { |
359 | 389 | expect( |
360 | 390 | resolveXaiWebSearchModel({ |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -290,6 +290,40 @@ describe("xai x_search tool", () => {
|
290 | 290 | expect(firstAuthorizationHeader(mockFetch)).toBe("Bearer xai-plugin-key"); |
291 | 291 | }); |
292 | 292 | |
| 293 | +it("reports malformed x_search JSON as a provider error", async () => { |
| 294 | +const mockFetch = vi.fn((_input?: unknown, _init?: unknown) => |
| 295 | +Promise.resolve({ |
| 296 | +ok: true, |
| 297 | +json: () => Promise.reject(new SyntaxError("Unexpected token")), |
| 298 | +} as Response), |
| 299 | +); |
| 300 | +global.fetch = withFetchPreconnect(mockFetch); |
| 301 | +const tool = createXSearchTool({ |
| 302 | +config: { |
| 303 | +plugins: { |
| 304 | +entries: { |
| 305 | +xai: { |
| 306 | +config: { |
| 307 | +webSearch: { |
| 308 | +apiKey: "xai-plugin-key", // pragma: allowlist secret |
| 309 | +}, |
| 310 | +xSearch: { |
| 311 | +enabled: true, |
| 312 | +}, |
| 313 | +}, |
| 314 | +}, |
| 315 | +}, |
| 316 | +}, |
| 317 | +}, |
| 318 | +}); |
| 319 | + |
| 320 | +await expect( |
| 321 | +tool?.execute?.("x-search:malformed-json", { |
| 322 | +query: "latest post from huntharo", |
| 323 | +}), |
| 324 | +).rejects.toThrow("xAI X search failed: malformed JSON response"); |
| 325 | +}); |
| 326 | + |
293 | 327 | it("prefers the active runtime config for shared xAI keys", async () => { |
294 | 328 | const mockFetch = installXSearchFetch(); |
295 | 329 | const tool = createXSearchTool({ |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。