[PR #6339] fix(acp): preserve file attachment metadata during session replay #11845

Closed
opened 2026-02-16 18:16:47 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/6339

State: closed
Merged: No


Problem

When a user attaches a file (e.g., a JSON config file) via an ACP client, the file displays correctly in the initial session. However, when the session is reloaded (via loadSession), the file attachment appears as plain text instead of as a proper attachment with filename and metadata.

Before this fix:

  • User attaches firebase.json → shows as attachment ✓
  • Session reload → shows as raw JSON text in the message body ✗

Root Cause

The ACP prompt function was storing text resources as plain text parts, which preserved the content for the LLM but lost the file metadata (filename, mimeType). The processMessage function (used for session replay) had no handler for file parts at all.

Solution

This PR fixes the issue with a two-part approach:

1. Store resources as file parts (agent.ts)

Text and binary resources are now stored as file parts with base64 data URLs:

case "resource": {
  const base64 = Buffer.from(resource.text, "utf-8").toString("base64")
  parts.push({
    type: "file",
    url: `data:${mime};base64,${base64}`,
    filename,
    mime,
  })
}

This preserves the filename and mimeType for proper replay.

2. Handle file parts during session replay (agent.ts)

Added handling in processMessage to replay file parts as ACP blocks:

  • Images → ACP image blocks
  • Text files → ACP resource blocks (decoded from base64)

3. Decode text-based files for LLM compatibility (message-v2.ts)

Since file parts now contain base64-encoded content, toModelMessage decodes text-based files before sending to the LLM:

if (isTextBased) {
  const text = Buffer.from(match[1], "base64").toString("utf-8")
  userMessage.parts.push({
    type: "text",
    text: `[File: ${part.filename || "file"}]\n${text}`,
  })
}

Binary files (images) are sent as file parts for multimodal models.

Alternatives Considered

  1. Store text resources as text parts (original behavior): Simpler, but loses file metadata for replay.

  2. Save attachments to disk and let LLM use read tool: Would require saving files to disk first, and the read tool doesn't work with data URLs.

  3. Store both text part and metadata separately: More complex data model changes.

The chosen approach is minimal and preserves both LLM compatibility and proper ACP replay.

Additional Fixes

  • Image filename preservation: Extracts filename from URI instead of hardcoding "image"
  • Style guide compliance: Removed else statements, replaced deprecated atob/btoa with Buffer

Testing

  • Verified file attachments display correctly on initial prompt
  • Verified file attachments replay correctly after session reload
  • Verified LLM receives decoded text content for text-based files
  • Verified images are sent as file parts for multimodal processing
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/6339 **State:** closed **Merged:** No --- ## Problem When a user attaches a file (e.g., a JSON config file) via an ACP client, the file displays correctly in the initial session. However, when the session is reloaded (via `loadSession`), the file attachment appears as plain text instead of as a proper attachment with filename and metadata. **Before this fix:** - User attaches `firebase.json` → shows as attachment ✓ - Session reload → shows as raw JSON text in the message body ✗ ## Root Cause The ACP `prompt` function was storing text resources as plain `text` parts, which preserved the content for the LLM but lost the file metadata (filename, mimeType). The `processMessage` function (used for session replay) had no handler for `file` parts at all. ## Solution This PR fixes the issue with a two-part approach: ### 1. Store resources as file parts (`agent.ts`) Text and binary resources are now stored as `file` parts with base64 data URLs: ```typescript case "resource": { const base64 = Buffer.from(resource.text, "utf-8").toString("base64") parts.push({ type: "file", url: `data:${mime};base64,${base64}`, filename, mime, }) } ``` This preserves the filename and mimeType for proper replay. ### 2. Handle file parts during session replay (`agent.ts`) Added handling in `processMessage` to replay file parts as ACP blocks: - Images → ACP `image` blocks - Text files → ACP `resource` blocks (decoded from base64) ### 3. Decode text-based files for LLM compatibility (`message-v2.ts`) Since file parts now contain base64-encoded content, `toModelMessage` decodes text-based files before sending to the LLM: ```typescript if (isTextBased) { const text = Buffer.from(match[1], "base64").toString("utf-8") userMessage.parts.push({ type: "text", text: `[File: ${part.filename || "file"}]\n${text}`, }) } ``` Binary files (images) are sent as file parts for multimodal models. ## Alternatives Considered 1. **Store text resources as text parts** (original behavior): Simpler, but loses file metadata for replay. 2. **Save attachments to disk and let LLM use `read` tool**: Would require saving files to disk first, and the `read` tool doesn't work with data URLs. 3. **Store both text part and metadata separately**: More complex data model changes. The chosen approach is minimal and preserves both LLM compatibility and proper ACP replay. ## Additional Fixes - **Image filename preservation**: Extracts filename from URI instead of hardcoding `"image"` - **Style guide compliance**: Removed `else` statements, replaced deprecated `atob`/`btoa` with `Buffer` ## Testing - Verified file attachments display correctly on initial prompt - Verified file attachments replay correctly after session reload - Verified LLM receives decoded text content for text-based files - Verified images are sent as file parts for multimodal processing
yindo added the pull-request label 2026-02-16 18:16:47 -05:00
yindo closed this issue 2026-02-16 18:16:47 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#11845