From 2a580cb413b40145cc3012c19f78d58612599b8f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 11 Aug 2026 12:15:46 -0400 Subject: [PATCH] fix(core): expose local attachment paths --- .../core/src/session/runner/to-llm-message.ts | 15 +++- .../core/test/session-runner-message.test.ts | 75 +++++++++++++++++-- 2 files changed, 81 insertions(+), 9 deletions(-) diff --git a/packages/core/src/session/runner/to-llm-message.ts b/packages/core/src/session/runner/to-llm-message.ts index cedd6aac5bd..991b2715c91 100644 --- a/packages/core/src/session/runner/to-llm-message.ts +++ b/packages/core/src/session/runner/to-llm-message.ts @@ -1,5 +1,6 @@ import { Message, ToolCallPart, ToolResultPart, type ContentPart, type ProviderMetadata } from "@opencode-ai/ai" import { Option, Schema } from "effect" +import { fileURLToPath } from "url" import type { Model } from "../../model" import { SessionMessage } from "../message" import type { FileAttachment } from "@opencode-ai/schema/prompt" @@ -14,6 +15,13 @@ const media = (file: FileAttachment): ContentPart => ({ metadata: file.description === undefined ? undefined : { description: file.description }, }) +const attachmentLocation = (file: FileAttachment) => { + if (file.source.type !== "uri") return undefined + const url = URL.parse(file.source.uri) + if (url?.protocol !== "file:") return undefined + return fileURLToPath(url) +} + const textAttachment = (file: FileAttachment): ContentPart => ({ type: "text", text: `\n\n${[ @@ -36,7 +44,7 @@ const textAttachment = (file: FileAttachment): ContentPart => ({ const directoryAttachment = (file: FileAttachment): ContentPart => ({ type: "text", text: `\n\n${[ - `Attached directory: ${file.name ?? (file.source.type === "uri" ? file.source.uri : "directory")}`, + `Attached directory: ${attachmentLocation(file) ?? file.name ?? (file.source.type === "uri" ? file.source.uri : "directory")}`, file.description === undefined ? undefined : `Description: ${file.description}`, file.data.length === 0 ? undefined : "", file.data.length === 0 ? undefined : Buffer.from(file.data, "base64").toString("utf8"), @@ -55,7 +63,10 @@ const directoryAttachment = (file: FileAttachment): ContentPart => ({ const attachmentContent = (file: FileAttachment): ContentPart[] => { if (file.mime === "text/plain") return [textAttachment(file)] if (file.mime === "application/x-directory") return [directoryAttachment(file)] - if (imageMimes.has(file.mime)) return [media(file)] + if (imageMimes.has(file.mime)) { + const location = attachmentLocation(file) + return [...(location === undefined ? [] : [Message.text(`Attached file: ${location}`)]), media(file)] + } return [] } diff --git a/packages/core/test/session-runner-message.test.ts b/packages/core/test/session-runner-message.test.ts index 93c93c8114b..0e014789ed1 100644 --- a/packages/core/test/session-runner-message.test.ts +++ b/packages/core/test/session-runner-message.test.ts @@ -249,13 +249,14 @@ Recent work ]) }) - test("lowers directory attachments as directory context", () => { + test("exposes admitted reference directory source paths in model context", () => { const directory = FileAttachment.make({ data: Base64.make(Buffer.from("lib/\nindex.ts").toString("base64")), mime: "application/x-directory", - source: { type: "uri", uri: "file:///project/src" }, - name: "src/", + source: { type: "uri", uri: "file:///references/harness-engineering" }, + name: "harness-engineering", }) + expect(directory.source).toEqual({ type: "uri", uri: "file:///references/harness-engineering" }) const messages = toLLMMessages( [ SessionMessage.User.make({ @@ -277,8 +278,8 @@ Recent work { type: "text", text: "Review this directory" }, { type: "text", - text: "\n\nAttached directory: src/\n\nlib/\nindex.ts", - metadata: { attachment: { source: directory.source, name: "src/" } }, + text: "\n\nAttached directory: /references/harness-engineering\n\nlib/\nindex.ts", + metadata: { attachment: { source: directory.source, name: "harness-engineering" } }, }, ], }) @@ -314,7 +315,7 @@ Recent work expect(messages).toHaveLength(1) expect(messages[0]?.content.map((part) => (part.type === "text" ? part.text : part.type))).toEqual([ "Review these attachments", - "\n\nAttached directory: src/\n\nindex.ts", + "\n\nAttached directory: /project/src\n\nindex.ts", "\n\nAttached file: main.ts\n\nexport const value = 1", ]) }) @@ -341,7 +342,9 @@ Recent work ) expect(messages).toHaveLength(1) - expect(messages[0]?.content).toMatchObject([{ type: "text", text: "\n\nAttached directory: src/\n\nindex.ts" }]) + expect(messages[0]?.content).toMatchObject([ + { type: "text", text: "\n\nAttached directory: /project/src\n\nindex.ts" }, + ]) }) test("uses materialized image data as provider media and drops unsupported attachments", () => { @@ -373,6 +376,64 @@ Recent work ]) }) + test("exposes admitted local image source paths before provider media", () => { + const data = Base64.make("AAECAw==") + const image = FileAttachment.make({ + data, + mime: "image/png", + source: { type: "uri", uri: "file:///project/IMG_3480.JPG" }, + name: "IMG_3480.JPG", + }) + expect(image.source).toEqual({ type: "uri", uri: "file:///project/IMG_3480.JPG" }) + + const messages = toLLMMessages( + [ + SessionMessage.User.make({ + id: id("user-local-image-path"), + type: "user", + text: "Inspect this image", + files: [image], + time: { created }, + }), + ], + model, + ) + + expect(messages[0]?.content).toEqual([ + { type: "text", text: "Inspect this image" }, + { type: "text", text: "Attached file: /project/IMG_3480.JPG" }, + { type: "media", mediaType: "image/png", data, filename: "IMG_3480.JPG" }, + ]) + }) + + test("does not add attachment location text for non-local provider media", () => { + const data = Base64.make("AAECAw==") + const messages = toLLMMessages( + [ + SessionMessage.User.make({ + id: id("user-remote-image"), + type: "user", + text: "Inspect this image", + files: [ + FileAttachment.make({ + data, + mime: "image/png", + source: { type: "uri", uri: "https://example.com/image.png" }, + name: "image.png", + }), + ], + time: { created }, + }), + ], + model, + ) + + expect(messages[0]?.content).toEqual([ + { type: "text", text: "Inspect this image" }, + { type: "media", mediaType: "image/png", data, filename: "image.png" }, + ]) + }) + test("deduplicates provider media while preserving durable attachment references", () => { const data = Base64.make("AAECAw==") const messages = toLLMMessages(