From 56c67bd9a876f7a89495d6d88245574612fc724e Mon Sep 17 00:00:00 2001 From: popsiclelmlm Date: Mon, 18 May 2026 00:18:52 +0800 Subject: [PATCH] fix: include saved image paths in CLI JSON output --- src/daemon/client.ts | 18 +++++++++++------ tests/daemon/client.test.ts | 39 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/daemon/client.ts b/src/daemon/client.ts index 61d921b18..40ddf932c 100644 --- a/src/daemon/client.ts +++ b/src/daemon/client.ts @@ -154,13 +154,8 @@ export async function handleResponse( if (response.isError) { return JSON.stringify(response.content); } - if (format === 'json') { - if (response.structuredContent) { - return JSON.stringify(response.structuredContent); - } - // Fall-through to text for backward compatibility. - } const chunks = []; + const images: Array<{filePath: string; mimeType: string}> = []; for (const content of response.content) { if (content.type === 'text') { chunks.push(content.text); @@ -181,10 +176,21 @@ export async function handleResponse( const name = crypto.randomUUID(); const filepath = await getTempFilePath(`${name}${extension}`); fs.writeFileSync(filepath, data); + images.push({filePath: filepath, mimeType}); chunks.push(`Saved to ${filepath}.`); } else { throw new Error('Not supported response content type'); } } + if (format === 'json') { + if (response.structuredContent) { + const structuredContent = { + ...response.structuredContent, + ...(images.length ? {images} : {}), + }; + return JSON.stringify(structuredContent); + } + // Fall-through to text for backward compatibility. + } return format === 'md' ? chunks.join(' ') : JSON.stringify(chunks); } diff --git a/tests/daemon/client.test.ts b/tests/daemon/client.test.ts index 7db5cfd1b..f263d9b9b 100644 --- a/tests/daemon/client.test.ts +++ b/tests/daemon/client.test.ts @@ -6,6 +6,8 @@ import assert from 'node:assert'; import crypto from 'node:crypto'; +import {existsSync, rmSync} from 'node:fs'; +import {dirname} from 'node:path'; import {describe, it, afterEach, beforeEach} from 'node:test'; import { @@ -127,6 +129,43 @@ describe('daemon client', () => { assert.ok(response.includes('.png')); }); + it('includes saved image file paths in structured JSON responses', async () => { + const imageContentResponse = { + content: [ + { + type: 'text' as const, + text: 'Took a screenshot.', + }, + { + type: 'image' as const, + data: Buffer.from('image data').toString('base64'), + mimeType: 'image/png', + }, + ], + structuredContent: { + message: 'Took a screenshot.', + }, + }; + let filePath: string | undefined; + try { + const response = await handleResponse(imageContentResponse, 'json'); + const parsed = JSON.parse(response) as { + message: string; + images: Array<{filePath: string; mimeType: string}>; + }; + assert.strictEqual(parsed.message, 'Took a screenshot.'); + assert.strictEqual(parsed.images.length, 1); + assert.strictEqual(parsed.images[0].mimeType, 'image/png'); + filePath = parsed.images[0].filePath; + assert.ok(filePath.endsWith('.png')); + assert.ok(existsSync(filePath)); + } finally { + if (filePath) { + rmSync(dirname(filePath), {recursive: true, force: true}); + } + } + }); + it('uses the webp extension for WebP images', async () => { const webpContentResponse = { content: [