From 8e2d422ffe56f3b2eb52e3f7195a2f9722a9fc46 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Tue, 23 Jun 2026 22:15:00 +0530 Subject: [PATCH] fix(acp): preserve resource text source (#33524) --- packages/opencode/src/acp/content.ts | 16 ++++++++++++++-- packages/opencode/test/acp/content.test.ts | 18 +++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/acp/content.ts b/packages/opencode/src/acp/content.ts index 5f149d85f..98060a3e2 100644 --- a/packages/opencode/src/acp/content.ts +++ b/packages/opencode/src/acp/content.ts @@ -1,6 +1,6 @@ import type { ContentBlock, ContentChunk, ResourceLink, Role } from "@agentclientprotocol/sdk" import path from "node:path" -import { pathToFileURL } from "node:url" +import { fileURLToPath, pathToFileURL } from "node:url" import { SessionV1 } from "@opencode-ai/core/v1/session" export type PromptPart = SessionV1.TextPartInput | SessionV1.FilePartInput @@ -76,7 +76,19 @@ export function contentBlockToParts(block: ContentBlock): PromptPart[] { case "resource": if ("text" in block.resource) { - return [{ type: "text", text: block.resource.text }] + try { + const parsed = new URL(block.resource.uri) + if (parsed.protocol === "file:") { + const line = parsed.hash.match(/^#L(\d+)/)?.[1] + return [ + { + type: "text", + text: `[${fileURLToPath(parsed)}${line ? `:${line}` : ""}]\n${block.resource.text}`, + }, + ] + } + } catch {} + return [{ type: "text", text: `[${block.resource.uri}]\n${block.resource.text}` }] } if (block.resource.mimeType) { return [ diff --git a/packages/opencode/test/acp/content.test.ts b/packages/opencode/test/acp/content.test.ts index 90f62f9d1..adf9ec2ef 100644 --- a/packages/opencode/test/acp/content.test.ts +++ b/packages/opencode/test/acp/content.test.ts @@ -99,17 +99,29 @@ describe("acp content conversion", () => { ]) }) - test("resource with text becomes a text part", () => { + test("resource with text becomes a sourced text part", () => { expect( contentBlockToParts({ type: "resource", resource: { - uri: "file:///tmp/context.txt", + uri: "file:///tmp/context.txt#L12-L14", mimeType: "text/plain", text: "context", }, }), - ).toEqual([{ type: "text", text: "context" }]) + ).toEqual([{ type: "text", text: "[/tmp/context.txt:12]\ncontext" }]) + }) + + test("resource with text uses URI fallback for non-file resources", () => { + expect( + contentBlockToParts({ + type: "resource", + resource: { + uri: "mcp://server/context", + text: "context", + }, + }), + ).toEqual([{ type: "text", text: "[mcp://server/context]\ncontext" }]) }) test("resource with blob and mimeType becomes a data URL file part", () => {