@@ -238,6 +238,43 @@ const correlateRuntimeEventWithInstance = (
238238 return { ...event , providerInstanceId : source . instanceId } ;
239239} ;
240240
241+ /**
242+ * Appends an on-disk path line for every attachment so the model's tools can
243+ * dereference the actual file.
244+ *
245+ * Every attachment also reaches the adapter, and each adapter decides what its
246+ * provider ingests natively: OpenCode sends generic files as file parts, the
247+ * others send images only and rely on these lines for everything else. That
248+ * makes the path line the sole channel a non-image attachment has on those
249+ * providers, which is why follow-ups need it exactly as much as turns do.
250+ *
251+ * Unresolvable ids are skipped here and surface as adapter errors when the file
252+ * is read.
253+ */
254+ const appendAttachmentPathLines = (
255+ attachmentsDir : string ,
256+ input : string | undefined ,
257+ attachments : ReadonlyArray < {
258+ readonly id : string ;
259+ readonly type : string ;
260+ readonly name : string ;
261+ } > ,
262+ ) : string | undefined => {
263+ const lines = attachments . flatMap ( ( attachment ) => {
264+ const attachmentPath = resolveAttachmentPath ( {
265+ attachmentsDir,
266+ attachment : attachment as Parameters < typeof resolveAttachmentPath > [ 0 ] [ "attachment" ] ,
267+ } ) ;
268+ return attachmentPath === null
269+ ? [ ]
270+ : [ `[Attached ${ attachment . type } "${ attachment . name } " is saved at: ${ attachmentPath } ]` ] ;
271+ } ) ;
272+ if ( lines . length === 0 ) return input ;
273+ return [ input , lines . join ( "\n" ) ]
274+ . filter ( ( part ) : part is string => typeof part === "string" && part . length > 0 )
275+ . join ( "\n\n" ) ;
276+ } ;
277+
241278const makeProviderService = Effect . fn ( "makeProviderService" ) ( function * (
242279 options ?: ProviderServiceLiveOptions ,
243280) {
@@ -773,27 +810,11 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (
773810 ) ;
774811 }
775812
776- // Every attachment gets an on-disk path in the prompt so the model's tools
777- // can dereference the actual file. All attachments then go to the adapter,
778- // and each adapter decides what its provider ingests natively: OpenCode
779- // sends generic files as file parts, the others send images only and rely
780- // on the path line for everything else. Unresolvable ids are skipped here
781- // and surface as adapter errors when the file is read.
782- const attachmentPathLines = attachments . flatMap ( ( attachment ) => {
783- const attachmentPath = resolveAttachmentPath ( {
784- attachmentsDir : serverConfig . attachmentsDir ,
785- attachment,
786- } ) ;
787- return attachmentPath === null
788- ? [ ]
789- : [ `[Attached ${ attachment . type } "${ attachment . name } " is saved at: ${ attachmentPath } ]` ] ;
790- } ) ;
791- const inputTextWithAttachmentPaths =
792- attachmentPathLines . length === 0
793- ? parsed . input
794- : [ parsed . input , attachmentPathLines . join ( "\n" ) ]
795- . filter ( ( part ) : part is string => typeof part === "string" && part . length > 0 )
796- . join ( "\n\n" ) ;
813+ const inputTextWithAttachmentPaths = appendAttachmentPathLines (
814+ serverConfig . attachmentsDir ,
815+ parsed . input ,
816+ attachments ,
817+ ) ;
797818
798819 const input = {
799820 ...parsed ,
@@ -1337,7 +1358,19 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (
13371358 "provider.thread_id" : input . threadId ,
13381359 "provider.attachment_count" : input . attachments . length ,
13391360 } ) ;
1340- return yield * followUpSession ( input ) ;
1361+ // Same path lines `sendTurn` appends. Without them a generic file attached
1362+ // to a follow-up is lost outright: every adapter except OpenCode skips
1363+ // non-images, so the path line is the only thing that tells the agent the
1364+ // file exists.
1365+ const followUpInputText = appendAttachmentPathLines (
1366+ serverConfig . attachmentsDir ,
1367+ input . input ,
1368+ input . attachments ,
1369+ ) ;
1370+ return yield * followUpSession ( {
1371+ ...input ,
1372+ ...( followUpInputText !== undefined ? { input : followUpInputText } : { } ) ,
1373+ } ) ;
13411374 } ) ;
13421375
13431376 const getSessionInputQueue : ProviderServiceMethod < "getSessionInputQueue" > = Effect . fn (
0 commit comments