From a709a6604f6cfce0fe2442ea967fc0c17de24154 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sat, 8 Aug 2026 09:04:04 +0000 Subject: [PATCH] fix(web-search): parsed double-encoded zai results Decoded the extra JSON string layer returned by Z.AI MCP search and kept structured payloads out of answer text. Added regression coverage for source extraction and plain prose preservation. Fixes #8000 --- packages/coding-agent/CHANGELOG.md | 4 ++++ .../coding-agent/src/web/search/providers/zai.ts | 15 ++++++++++++--- packages/coding-agent/test/web/search/zai.test.ts | 15 ++++++++++----- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 1cb7c55d0..38c3985a2 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Z.AI web search dropping sources and exposing raw JSON when MCP responses double-encode content text ([#8000](https://github.com/can1357/oh-my-pi/issues/8000)). + ## [17.2.11] - 2026-08-07 ### Added diff --git a/packages/coding-agent/src/web/search/providers/zai.ts b/packages/coding-agent/src/web/search/providers/zai.ts index ff8e98b92..39d052fa3 100644 --- a/packages/coding-agent/src/web/search/providers/zai.ts +++ b/packages/coding-agent/src/web/search/providers/zai.ts @@ -353,11 +353,20 @@ function parseSearchPayload(rawResult: unknown): { for (const part of content) { const text = isRecord(part) ? asString(part.text) : null; if (!text) continue; - textParts.push(text); try { - candidates.push(JSON.parse(text)); + let parsed: unknown = JSON.parse(text); + if (typeof parsed === "string") { + try { + parsed = JSON.parse(parsed); + } catch { + // The decoded string is answer text rather than another JSON payload. + } + } + candidates.push(parsed); + if (getSearchResults(parsed).length === 0) textParts.push(text); } catch { - // Not JSON payload; keep as fallback answer text. + // Non-JSON content is preserved as answer text. + textParts.push(text); } } } diff --git a/packages/coding-agent/test/web/search/zai.test.ts b/packages/coding-agent/test/web/search/zai.test.ts index 7c98466a0..27b0e54bd 100644 --- a/packages/coding-agent/test/web/search/zai.test.ts +++ b/packages/coding-agent/test/web/search/zai.test.ts @@ -9,7 +9,7 @@ interface CapturedRequest { } describe("Z.AI web search provider", () => { - it("initializes a Streamable HTTP MCP session before calling web_search_prime", async () => { + it("initializes a Streamable HTTP MCP session and parses doubly encoded results", async () => { const capturedRequests: CapturedRequest[] = []; const fetchImpl: FetchImpl = (_input, init) => { const request = { @@ -53,16 +53,20 @@ describe("Z.AI web search provider", () => { content: [ { type: "text", - text: JSON.stringify({ - search_result: [ + text: JSON.stringify( + JSON.stringify([ { title: "Z.AI search result", content: "Search result content", link: "https://example.com/zai", media: "Example", }, - ], - }), + ]), + ), + }, + { + type: "text", + text: "Plain prose answer.", }, ], }, @@ -107,6 +111,7 @@ describe("Z.AI web search provider", () => { author: "Example", }, ]); + expect(response.answer).toBe("Plain prose answer."); }); function createMcpFetch(): { fetchImpl: FetchImpl; capturedRequests: CapturedRequest[] } {