From 0b39f9ed35f2160c953c97559f3e9acbbd579b17 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Wed, 24 Jun 2026 12:31:55 +0200 Subject: [PATCH 1/2] fix: Use inline data for local image URIs Closes #231 --- src/CodexAcpClient.ts | 18 +++++++++++++++++- .../CodexACPAgent/CodexAcpClient.test.ts | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index 8db93a13..30e9009c 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -703,7 +703,7 @@ function buildPromptItems(prompt: acp.ContentBlock[]): UserInput[] { case "text": return {type: "text", text: block.text, text_elements: []}; case "image": { - const url = block.uri ?? `data:${block.mimeType};base64,${block.data}`; + const url = shouldUseImageUri(block.uri) ? block.uri : imageDataUrl(block); return {type: "image", url}; } case "resource_link": @@ -729,6 +729,22 @@ function buildPromptItems(prompt: acp.ContentBlock[]): UserInput[] { }).filter((block): block is UserInput => block !== null); } +function imageDataUrl(block: acp.ContentBlock & { type: "image" }): string { + return `data:${block.mimeType};base64,${block.data}`; +} + +function shouldUseImageUri(uri: string | null | undefined): uri is string { + if (!uri) { + return false; + } + try { + const protocol = new URL(uri).protocol; + return protocol === "http:" || protocol === "https:"; + } catch { + return false; + } +} + function isImageMimeType(mimeType: string | null | undefined): mimeType is string { return mimeType?.startsWith("image/") ?? false; } diff --git a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts index 4e0aaa27..3033ba23 100644 --- a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts +++ b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts @@ -1412,6 +1412,24 @@ describe('ACP server test', { timeout: 40_000 }, () => { })); }); + it ('should use inline image data when image uri is a local file', async () => { + const { mockFixture, turnStartSpy } = setupPromptFixture({ + supportedInputModalities: ["text", "image"], + }); + + const prompt: acp.ContentBlock[] = [ + { type: "image", mimeType: "image/png", data: "abc123", uri: "file:///Users/test/Desktop/Screenshot%201.png" }, + ]; + + await mockFixture.getCodexAcpAgent().prompt({ sessionId: "id", prompt }); + + expect(turnStartSpy).toHaveBeenCalledWith(expect.objectContaining({ + input: [ + { type: "image", url: "data:image/png;base64,abc123" }, + ] + })); + }); + it ('should show rate limits from multiple sources in status', async () => { const rateLimits: RateLimitsMap = new Map(); rateLimits.set("limit-1", { From a85120a3df433818127232c660fdeee157c2c19c Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Wed, 24 Jun 2026 13:11:59 +0200 Subject: [PATCH 2/2] Support data URLs for image prompts --- src/CodexAcpClient.ts | 6 +++--- .../CodexACPAgent/CodexAcpClient.test.ts | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index 30e9009c..7655dedc 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -703,7 +703,7 @@ function buildPromptItems(prompt: acp.ContentBlock[]): UserInput[] { case "text": return {type: "text", text: block.text, text_elements: []}; case "image": { - const url = shouldUseImageUri(block.uri) ? block.uri : imageDataUrl(block); + const url = isSupportedImageUrl(block.uri) ? block.uri : imageDataUrl(block); return {type: "image", url}; } case "resource_link": @@ -733,13 +733,13 @@ function imageDataUrl(block: acp.ContentBlock & { type: "image" }): string { return `data:${block.mimeType};base64,${block.data}`; } -function shouldUseImageUri(uri: string | null | undefined): uri is string { +function isSupportedImageUrl(uri: string | null | undefined): uri is string { if (!uri) { return false; } try { const protocol = new URL(uri).protocol; - return protocol === "http:" || protocol === "https:"; + return protocol === "http:" || protocol === "https:" || protocol === "data:"; } catch { return false; } diff --git a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts index 3033ba23..c5f4ceb7 100644 --- a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts +++ b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts @@ -1430,6 +1430,24 @@ describe('ACP server test', { timeout: 40_000 }, () => { })); }); + it ('should use inline image data when image uri is client-internal', async () => { + const { mockFixture, turnStartSpy } = setupPromptFixture({ + supportedInputModalities: ["text", "image"], + }); + + const prompt: acp.ContentBlock[] = [ + { type: "image", mimeType: "image/png", data: "abc123", uri: "zed:///agent/pasted-image?name=Image" }, + ]; + + await mockFixture.getCodexAcpAgent().prompt({ sessionId: "id", prompt }); + + expect(turnStartSpy).toHaveBeenCalledWith(expect.objectContaining({ + input: [ + { type: "image", url: "data:image/png;base64,abc123" }, + ] + })); + }); + it ('should show rate limits from multiple sources in status', async () => { const rateLimits: RateLimitsMap = new Map(); rateLimits.set("limit-1", {