@@ -12,7 +12,6 @@ import {
1212 type ContentBlock ,
1313 type InitializeRequest ,
1414 ndJsonStream ,
15- type NewSessionRequest ,
1615 PROTOCOL_VERSION ,
1716 type ReadTextFileRequest ,
1817 type ReadTextFileResponse ,
@@ -210,78 +209,58 @@ export class ACPLanguageModel implements LanguageModelV2 {
210209 }
211210
212211 /**
213- * Converts AI SDK prompt messages into an array of ACP ContentBlock objects.
212+ * Converts AI SDK prompt messages into ACP ContentBlock objects.
213+ * When session exists, only extracts the last user message (history is in session).
214+ * Prefixes text with role since ACP ContentBlock has no role field.
214215 */
215216 private getPromptContent (
216217 options : LanguageModelV2CallOptions ,
217218 ) : ContentBlock [ ] {
218- const contentBlocks : ContentBlock [ ] = [ ] ;
219+ // With persistent session, only send the latest user message
220+ const messages = this . sessionId
221+ ? options . prompt . filter ( ( m ) => m . role === "user" ) . slice ( - 1 )
222+ : options . prompt ;
219223
220- for ( const msg of options . prompt ) {
221- let prefix = "" ;
222- // Note: ACP doesn't have a "system" role, so we prefix it.
223- if ( msg . role === "system" ) {
224- prefix = "System: " ;
225- } else if ( msg . role === "user" ) {
226- prefix = "User: " ;
227- } else if ( msg . role === "assistant" ) {
228- prefix = "Assistant: " ;
229- }
224+ const contentBlocks : ContentBlock [ ] = [ ] ;
230225
231- // Note: ACP doesn't have a "tool" role. Tool results are handled
232- // by the agent itself, not by sending a message.
233- if (
234- msg . role === "system" ||
235- msg . role === "user" ||
236- msg . role === "assistant"
237- ) {
238- if ( Array . isArray ( msg . content ) ) {
239- for ( const part of msg . content ) {
240- switch ( part . type ) {
241- case "text" : {
242- contentBlocks . push ( {
243- type : "text" as const ,
244- text : `${ prefix } ${ part . text } ` ,
245- } ) ;
246- prefix = "" ; // Only prefix the first part
247- break ;
248- }
249- case "file" : {
250- const type : ContentBlock [ "type" ] | null = ( ( ) => {
251- if ( part . mediaType . startsWith ( "image/" ) ) {
252- return "image" ;
253- }
254- if ( part . mediaType . startsWith ( "audio/" ) ) {
255- return "audio" ;
256- }
257- return null ;
258- } ) ( ) ;
259-
260- if (
261- type === null ||
262- // Ensure data is string before processing
263- typeof part . data !== "string"
264- ) {
265- break ;
266- }
267- contentBlocks . push ( {
268- type,
269- mimeType : part . mediaType ,
270- data : extractBase64Data ( part . data ) ,
271- } ) ;
272-
273- break ;
274- }
226+ for ( const msg of messages ) {
227+ // Skip tool role - ACP handles tool results internally
228+ if ( msg . role === "tool" ) continue ;
229+
230+ // Prefix to identify role since ACP has no role field
231+ const prefix = msg . role === "system"
232+ ? "System: "
233+ : msg . role === "assistant"
234+ ? "Assistant: "
235+ : "" ;
236+
237+ if ( Array . isArray ( msg . content ) ) {
238+ let isFirst = true ;
239+ for ( const part of msg . content ) {
240+ if ( part . type === "text" ) {
241+ const text = isFirst ? `${ prefix } ${ part . text } ` : part . text ;
242+ contentBlocks . push ( { type : "text" as const , text } ) ;
243+ isFirst = false ;
244+ } else if ( part . type === "file" && typeof part . data === "string" ) {
245+ const type = part . mediaType . startsWith ( "image/" )
246+ ? "image"
247+ : part . mediaType . startsWith ( "audio/" )
248+ ? "audio"
249+ : null ;
250+ if ( type ) {
251+ contentBlocks . push ( {
252+ type,
253+ mimeType : part . mediaType ,
254+ data : extractBase64Data ( part . data ) ,
255+ } ) ;
275256 }
276-
277- // Other parts (like images) are ignored in current implementation
278257 }
279- } else if ( typeof msg . content === "string" ) {
280- contentBlocks . push ( {
281- type : "text" as const ,
282- text : `${ prefix } ${ msg . content } ` ,
283- } ) ;
284258 }
259+ } else if ( typeof msg . content === "string" ) {
260+ contentBlocks . push ( {
261+ type : "text" as const ,
262+ text : `${ prefix } ${ msg . content } ` ,
263+ } ) ;
285264 }
286265 }
287266
@@ -352,25 +331,46 @@ export class ACPLanguageModel implements LanguageModelV2 {
352331 ) ;
353332 }
354333
355- const sessionConfig : NewSessionRequest = {
356- ...this . config . session ,
357- cwd : this . config . session . cwd ?? sessionCwd ,
358- mcpServers : this . config . session . mcpServers ?? [ ] ,
359- } ;
334+ if ( this . config . existingSessionId ) {
335+ await this . connection . loadSession ( {
336+ sessionId : this . config . existingSessionId ,
337+ cwd : this . config . session ?. cwd ?? sessionCwd ,
338+ mcpServers : this . config . session ?. mcpServers ?? [ ] ,
339+ } ) ;
340+ this . sessionId = this . config . existingSessionId ;
341+ } else {
342+ const session = await this . connection . newSession ( {
343+ ...this . config . session ,
344+ cwd : this . config . session ?. cwd ?? sessionCwd ,
345+ mcpServers : this . config . session ?. mcpServers ?? [ ] ,
346+ } ) ;
347+ this . sessionId = session . sessionId ;
348+ }
349+ }
360350
361- const session = await this . connection . newSession ( sessionConfig ) ;
351+ /**
352+ * Clears connection state. Skips if persistSession is enabled.
353+ */
354+ private cleanup ( ) : void {
355+ if ( this . config . persistSession ) return ;
356+ this . forceCleanup ( ) ;
357+ }
362358
363- this . sessionId = session . sessionId ;
359+ /**
360+ * Returns the current session ID.
361+ */
362+ getSessionId ( ) : string | null {
363+ return this . sessionId ;
364364 }
365365
366366 /**
367- * Kills the agent process and clears connection state .
367+ * Forces cleanup regardless of persistSession setting .
368368 */
369- private cleanup ( ) : void {
369+ forceCleanup ( ) : void {
370370 if ( this . agentProcess ) {
371371 this . agentProcess . kill ( ) ;
372- this . agentProcess ! . stdin ?. end ( ) ;
373- this . agentProcess ! . stdout ?. destroy ( ) ;
372+ this . agentProcess . stdin ?. end ( ) ;
373+ this . agentProcess . stdout ?. destroy ( ) ;
374374 this . agentProcess = null ;
375375 }
376376 this . connection = null ;
0 commit comments