fix: include saved image paths in CLI JSON output (#2070)

Fixes #1910

Summary:
- save image content before returning structured CLI JSON output
- include saved image metadata in `structuredContent.images`
- add coverage for JSON output with image content

Tests:
- `npm test -- tests/daemon/client.test.ts`
- `npx eslint src/daemon/client.ts tests/daemon/client.test.ts && npx
prettier --check src/daemon/client.ts tests/daemon/client.test.ts`

Co-authored-by: Nicholas Roscino <nroscino@google.com>
This commit is contained in:
lmlm
2026-05-18 21:46:41 +08:00
committed by GitHub
parent a2083a2edd
commit a9fb555c80
2 changed files with 51 additions and 6 deletions
+12 -6
View File
@@ -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);
}
+39
View File
@@ -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: [