opencode icon indicating copy to clipboard operation
opencode copied to clipboard

fix(acp): preserve file attachment metadata during session replay

Open liorshk opened this issue 2 weeks ago • 0 comments

Problem

When loading a previous session via ACP, file attachments are not replayed correctly:

  1. Text files (JSON, plain text, etc.) are not sent as resource blocks
  2. Binary files (PDFs, audio, video) are not sent as resource blocks
  3. Synthetic text parts leak to ACP clients during replay
  4. Non-binary file parts cause LLM provider errors when passed through
  5. Audio content in prompts is not handled

Root Cause

In processMessage(), there was no handling for file type parts during session replay - they were silently skipped. Additionally:

  • Text parts were sent without checking part.synthetic
  • In toModelMessage(), all file parts were sent to the LLM regardless of MIME type
  • No handling for ACP audio content type in prompts

Solution

Option 1: Blacklist text MIME types

List specific text types and handle them differently.

Problem: Fragile - new types would break, and the list keeps growing.

Option 2: Whitelist binary types (chosen)

Categorize content by what LLMs and ACP clients actually support.

Benefits:

  • Robust: Unknown MIME types have safe fallback behavior
  • Provider-agnostic: Matches what LLM APIs actually accept
  • Simple: Clear categories instead of unbounded lists

Changes

1. Replay file parts as ACP content blocks (agent.ts - processMessage)

  • Images: Send as image block with base64 data
  • Binary files (PDF, audio, video): Send as resource block with blob field
  • Text files: Decode and send as resource block with text field

2. Add audio content support (agent.ts - prompt)

Handle ACP audio content type (per ACP spec):

case "audio":
  if (part.data) {
    parts.push({
      type: "file",
      url: `data:${part.mimeType};base64,${part.data}`,
      filename: `audio.${ext}`,
      mime: part.mimeType,
    })
  }
  break

3. Filter synthetic parts during replay (agent.ts)

if (part.text && !part.synthetic) {
  // Send to ACP
}

4. Whitelist binary content for LLM (message-v2.ts)

const isBinaryContent =
  part.mime.startsWith("image/") ||
  part.mime.startsWith("audio/") ||
  part.mime.startsWith("video/") ||
  part.mime === "application/pdf"

if (isBinaryContent) {
  // Keep as file part for LLM
} else {
  // Decode to text with filename header
}

Summary

Content Type ACP Replay LLM Input
image/* image block (data) file part
audio/* resource block (blob) file part
video/* resource block (blob) file part
application/pdf resource block (blob) file part
text/*, application/json, etc. resource block (text) decoded to text
Unknown resource block (text) decoded to text

liorshk avatar Dec 29 '25 08:12 liorshk