diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 41f82f406..1bd069388 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -10,6 +10,7 @@ - Fixed task subagent assistant turns being omitted from the per-model TPS/TTFT aggregates shown by `/models`. ([#8022](https://github.com/can1357/oh-my-pi/issues/8022)) - Fixed terminal-title spinner writes consuming CPU during WSL/ConPTY agent waits by using the same static working separator as native Windows ([#8012](https://github.com/can1357/oh-my-pi/issues/8012)). - Fixed long-running sessions leaking memory for every completed keep-alive `task`/scout subagent: a disposed (parked) subagent's `AgentSession` stayed pinned through the lifecycle adoption record's reviver closure, and `dispose()` never released the message array, append-only provider transcript, session-manager entries, or the raw-SSE debug buffer, so heavy transcripts and captured provider wire frames accumulated for the process lifetime ([#8003](https://github.com/can1357/oh-my-pi/issues/8003)). +- 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 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[] } {