diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 7cba78dd891..77a7b6c0cc8 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -741,13 +741,23 @@ export const layer = Layer.effect( }) } else if ("blob" in c && c.blob) { const mime = ("mimeType" in c ? c.mimeType : undefined) ?? part.mime + const url = `data:${mime};base64,${c.blob}` + if (mime === "text/plain") { + pieces.push({ + messageID: info.id, + sessionID: input.sessionID, + type: "text", + synthetic: true, + text: decodeDataUrl(url), + }) + } pieces.push({ messageID: info.id, sessionID: input.sessionID, type: "file", mime, filename: part.filename, - url: `data:${mime};base64,${c.blob}`, + url, }) } } diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 25a0d7b81d5..1a77bb03309 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -258,6 +258,14 @@ const resourceNoLLMServer = testEffect( text: "# MCP resource fixture", }, ] + : uri === "opencode-fixture://text-blob" + ? [ + { + uri, + mimeType: "text/plain", + blob: Buffer.from("MCP text blob fixture").toString("base64"), + }, + ] : [ { uri, @@ -2075,6 +2083,18 @@ resourceNoLLMServer.instance( text: { value: "@fixture-guide", start: 0, end: 14 }, }, }, + { + type: "file", + mime: "text/plain", + url: "opencode-fixture://text-blob", + filename: "fixture-text-blob", + source: { + type: "resource", + clientName: "resource-only-fixture", + uri: "opencode-fixture://text-blob", + text: { value: "@fixture-text-blob", start: 15, end: 33 }, + }, + }, { type: "file", mime: "image/png", @@ -2091,6 +2111,16 @@ resourceNoLLMServer.instance( }) expect(message.parts.some((part) => part.type === "text" && part.text === "# MCP resource fixture")).toBe(true) + expect(message.parts.some((part) => part.type === "text" && part.text === "MCP text blob fixture")).toBe(true) + expect( + message.parts.some( + (part) => + part.type === "file" && + !part.source && + part.mime === "text/plain" && + part.url.startsWith("data:text/plain;base64,"), + ), + ).toBe(true) expect( message.parts.some( (part) => part.type === "file" && part.source?.type === "resource" && part.url === "opencode-fixture://pixel",