From 0587e4cc73290674c32cc9f6edff11ff1b2e9178 Mon Sep 17 00:00:00 2001 From: yinghaosang Date: Tue, 17 Feb 2026 04:14:43 +0800 Subject: [PATCH] fix(agents): restrict MEDIA: token parsing to line start in tool results (#18510) --- .../pi-embedded-subscribe.tools.media.test.ts | 88 +++++++++++++++++++ src/agents/pi-embedded-subscribe.tools.ts | 28 +++--- 2 files changed, 105 insertions(+), 11 deletions(-) diff --git a/src/agents/pi-embedded-subscribe.tools.media.test.ts b/src/agents/pi-embedded-subscribe.tools.media.test.ts index f51e1e145..3452830f2 100644 --- a/src/agents/pi-embedded-subscribe.tools.media.test.ts +++ b/src/agents/pi-embedded-subscribe.tools.media.test.ts @@ -129,4 +129,92 @@ describe("extractToolResultMediaPaths", () => { }; expect(extractToolResultMediaPaths(result)).toEqual([]); }); + + it("does not match placeholder as a MEDIA: token", () => { + const result = { + content: [ + { + type: "text", + text: " placeholder with successful preflight voice transcript", + }, + ], + }; + expect(extractToolResultMediaPaths(result)).toEqual([]); + }); + + it("does not match placeholder as a MEDIA: token", () => { + const result = { + content: [{ type: "text", text: " (2 images)" }], + }; + expect(extractToolResultMediaPaths(result)).toEqual([]); + }); + + it("does not match other media placeholder variants", () => { + for (const tag of [ + "", + "", + "", + "", + ]) { + const result = { + content: [{ type: "text", text: `${tag} some context` }], + }; + expect(extractToolResultMediaPaths(result)).toEqual([]); + } + }); + + it("does not match mid-line MEDIA: in documentation text", () => { + const result = { + content: [ + { + type: "text", + text: 'Use MEDIA: "https://example.com/voice.ogg", asVoice: true to send voice', + }, + ], + }; + expect(extractToolResultMediaPaths(result)).toEqual([]); + }); + + it("still extracts MEDIA: at line start after other text lines", () => { + const result = { + content: [ + { + type: "text", + text: "Generated screenshot\nMEDIA:/tmp/screenshot.png\nDone", + }, + ], + }; + expect(extractToolResultMediaPaths(result)).toEqual(["/tmp/screenshot.png"]); + }); + + it("extracts indented MEDIA: line", () => { + const result = { + content: [{ type: "text", text: " MEDIA:/tmp/indented.png" }], + }; + expect(extractToolResultMediaPaths(result)).toEqual(["/tmp/indented.png"]); + }); + + it("extracts valid MEDIA: line while ignoring on another line", () => { + const result = { + content: [ + { + type: "text", + text: " was transcribed\nMEDIA:/tmp/tts-output.opus\nDone", + }, + ], + }; + expect(extractToolResultMediaPaths(result)).toEqual(["/tmp/tts-output.opus"]); + }); + + it("extracts multiple MEDIA: lines from a single text block", () => { + const result = { + content: [ + { + type: "text", + text: "MEDIA:/tmp/page1.png\nSome text\nMEDIA:/tmp/page2.png", + }, + ], + }; + expect(extractToolResultMediaPaths(result)).toEqual(["/tmp/page1.png", "/tmp/page2.png"]); + }); }); diff --git a/src/agents/pi-embedded-subscribe.tools.ts b/src/agents/pi-embedded-subscribe.tools.ts index a46791835..6b8cd3219 100644 --- a/src/agents/pi-embedded-subscribe.tools.ts +++ b/src/agents/pi-embedded-subscribe.tools.ts @@ -153,17 +153,23 @@ export function extractToolResultMediaPaths(result: unknown): string[] { continue; } if (entry.type === "text" && typeof entry.text === "string") { - // Reset lastIndex since MEDIA_TOKEN_RE is global. - MEDIA_TOKEN_RE.lastIndex = 0; - let match: RegExpExecArray | null; - while ((match = MEDIA_TOKEN_RE.exec(entry.text)) !== null) { - // Strip surrounding quotes/backticks and whitespace (mirrors cleanCandidate in media/parse). - const p = match[1] - ?.replace(/^[`"'[{(]+/, "") - .replace(/[`"'\]})\\,]+$/, "") - .trim(); - if (p && p.length <= 4096) { - paths.push(p); + // Only parse lines that start with MEDIA: (after trimming) to avoid + // false-matching placeholders like or mid-line mentions. + // Mirrors the line-start guard in splitMediaFromOutput (media/parse.ts). + for (const line of entry.text.split("\n")) { + if (!line.trimStart().startsWith("MEDIA:")) { + continue; + } + MEDIA_TOKEN_RE.lastIndex = 0; + let match: RegExpExecArray | null; + while ((match = MEDIA_TOKEN_RE.exec(line)) !== null) { + const p = match[1] + ?.replace(/^[`"'[{(]+/, "") + .replace(/[`"'\]})\\,]+$/, "") + .trim(); + if (p && p.length <= 4096) { + paths.push(p); + } } } }