3939# default 64 KB stream buffer limit.
4040STDIO_BUFFER_LIMIT = 100 * 1024 * 1024
4141
42+ IMAGE_MIME_BY_EXT : dict [str , str ] = {
43+ ".png" : "image/png" ,
44+ ".jpg" : "image/jpeg" ,
45+ ".jpeg" : "image/jpeg" ,
46+ ".gif" : "image/gif" ,
47+ ".webp" : "image/webp" ,
48+ }
49+
50+ NON_IMAGE_MIME : dict [str , str ] = {
51+ "pdf" : "application/pdf" ,
52+ "xlsx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ,
53+ }
54+
55+ EMPTY_FROZENSET : frozenset [str ] = frozenset ()
56+
57+ # File types each agent can consume natively in the ACP prompt —
58+ # no sandbox-path note needed for these since the content is inline.
59+ NATIVE_FILE_TYPES : dict [AgentKind , frozenset [str ]] = {
60+ AgentKind .CLAUDE : frozenset ({"image" , "pdf" }),
61+ AgentKind .CODEX : frozenset ({"image" }),
62+ }
63+
4264
4365@dataclass
4466class AcpSessionConfig :
@@ -102,7 +124,7 @@ async def send_prompt(
102124 TextContentBlock (type = "text" , text = content ),
103125 ]
104126 if attachments :
105- attachment_note = self ._build_attachment_note (attachments )
127+ attachment_note = self ._build_attachment_note (attachments , agent_kind )
106128 if attachment_note :
107129 prompt_blocks .append (
108130 TextContentBlock (type = "text" , text = attachment_note )
@@ -121,23 +143,17 @@ async def send_prompt(
121143 finally :
122144 self ._handler .finish (prompt_completed = prompt_completed )
123145
124- IMAGE_MIME_BY_EXT : dict [str , str ] = {
125- ".png" : "image/png" ,
126- ".jpg" : "image/jpeg" ,
127- ".jpeg" : "image/jpeg" ,
128- ".gif" : "image/gif" ,
129- ".webp" : "image/webp" ,
130- }
131-
132- NON_IMAGE_MIME : dict [str , str ] = {
133- "pdf" : "application/pdf" ,
134- "xlsx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ,
135- }
136-
137146 @staticmethod
138- def _build_attachment_note (attachments : list [dict [str , Any ]]) -> str :
147+ def _build_attachment_note (
148+ attachments : list [dict [str , Any ]],
149+ agent_kind : AgentKind ,
150+ ) -> str :
151+ native_types = NATIVE_FILE_TYPES .get (agent_kind , EMPTY_FROZENSET )
139152 lines : list [str ] = []
140153 for att in attachments :
154+ file_type = att .get ("file_type" , "" )
155+ if file_type in native_types :
156+ continue
141157 file_path = att .get ("file_path" )
142158 if not isinstance (file_path , str ) or not file_path :
143159 continue
@@ -170,14 +186,8 @@ def _build_attachment_blocks(
170186 if not file_path :
171187 continue
172188 file_type = att .get ("file_type" , "" )
173- # Native ACP attachment support differs by agent: Claude can consume
174- # images and PDFs in the prompt, while Codex only handles images.
175- # Other uploaded files still exist in the sandbox for tool-based reads.
176- if file_type == "image" :
177- pass
178- elif file_type == "pdf" and agent_kind == AgentKind .CLAUDE :
179- pass
180- else :
189+ # Only include file types this agent can consume natively in the prompt.
190+ if file_type not in NATIVE_FILE_TYPES .get (agent_kind , EMPTY_FROZENSET ):
181191 continue
182192 full_path = storage_path / file_path
183193 try :
@@ -190,7 +200,7 @@ def _build_attachment_blocks(
190200
191201 if file_type == "image" :
192202 ext = Path (filename ).suffix .lower ()
193- mime = AcpSession . IMAGE_MIME_BY_EXT .get (ext , "image/png" )
203+ mime = IMAGE_MIME_BY_EXT .get (ext , "image/png" )
194204 blocks .append (
195205 ImageContentBlock (
196206 type = "image" ,
@@ -199,9 +209,7 @@ def _build_attachment_blocks(
199209 )
200210 )
201211 else :
202- mime = AcpSession .NON_IMAGE_MIME .get (
203- file_type , "application/octet-stream"
204- )
212+ mime = NON_IMAGE_MIME .get (file_type , "application/octet-stream" )
205213 blocks .append (
206214 EmbeddedResourceContentBlock (
207215 type = "resource" ,
0 commit comments