@@ -7,9 +7,18 @@ import type {
77 OpencodeClient ,
88 Part ,
99 SessionMessageResponse ,
10+ ToolPart ,
1011} from "@opencode-ai/sdk/v2"
1112import { Effect } from "effect"
1213import { ACPNextSession } from "./session"
14+ import {
15+ duplicateRunningToolUpdate ,
16+ errorToolUpdate ,
17+ pendingToolCall ,
18+ runningToolUpdate ,
19+ shellOutputSnapshot ,
20+ completedToolUpdate ,
21+ } from "./tool"
1322
1423const log = Log . create ( { service : "acp-next-event" } )
1524
@@ -29,6 +38,8 @@ export function start(input: { sdk: OpencodeClient; connection: Connection; sess
2938
3039export class Subscription {
3140 private readonly abort = new AbortController ( )
41+ private readonly shellSnapshots = new Map < string , string > ( )
42+ private readonly toolStarts = new Set < string > ( )
3243 private started = false
3344
3445 constructor (
@@ -61,6 +72,17 @@ export class Subscription {
6172 }
6273 }
6374
75+ async replayMessage ( message : SessionMessageResponse ) {
76+ if ( message . info . role !== "assistant" && message . info . role !== "user" ) return
77+
78+ for ( const part of message . parts ) {
79+ await this . recordFetchedPart ( message . info . sessionID , message , part )
80+ if ( part . type === "tool" ) {
81+ await this . handleToolPart ( message . info . sessionID , part )
82+ }
83+ }
84+ }
85+
6486 private async run ( ) {
6587 while ( ! this . abort . signal . aborted ) {
6688 const events = ( await this . input . sdk . global . event ( {
@@ -96,6 +118,9 @@ export class Subscription {
96118 metadata : "metadata" in part ? part . metadata : undefined ,
97119 } ) ,
98120 )
121+ if ( part . type === "tool" ) {
122+ await this . handleToolPart ( session . id , part )
123+ }
99124 }
100125
101126 private async handlePartDelta ( event : EventMessagePartDelta ) {
@@ -181,6 +206,106 @@ export class Subscription {
181206 } ) ,
182207 )
183208 }
209+
210+ private async handleToolPart ( sessionId : string , part : ToolPart ) {
211+ await this . toolStart ( sessionId , part )
212+
213+ switch ( part . state . status ) {
214+ case "pending" :
215+ this . shellSnapshots . delete ( part . callID )
216+ return
217+
218+ case "running" :
219+ await this . runningTool ( sessionId , part )
220+ return
221+
222+ case "completed" :
223+ this . clearTool ( part . callID )
224+ await this . input . connection . sessionUpdate ( {
225+ sessionId,
226+ update : {
227+ sessionUpdate : "tool_call_update" ,
228+ ...completedToolUpdate ( {
229+ toolCallId : part . callID ,
230+ toolName : part . tool ,
231+ state : part . state ,
232+ } ) ,
233+ } ,
234+ } )
235+ return
236+
237+ case "error" :
238+ this . clearTool ( part . callID )
239+ await this . input . connection . sessionUpdate ( {
240+ sessionId,
241+ update : {
242+ sessionUpdate : "tool_call_update" ,
243+ ...errorToolUpdate ( {
244+ toolCallId : part . callID ,
245+ toolName : part . tool ,
246+ state : part . state ,
247+ } ) ,
248+ } ,
249+ } )
250+ return
251+ }
252+ }
253+
254+ private async runningTool ( sessionId : string , part : ToolPart ) {
255+ if ( part . state . status !== "running" ) return
256+
257+ const output = part . tool === "bash" ? shellOutputSnapshot ( part . state ) : undefined
258+ if ( output !== undefined ) {
259+ if ( this . shellSnapshots . get ( part . callID ) === output ) {
260+ await this . input . connection . sessionUpdate ( {
261+ sessionId,
262+ update : {
263+ sessionUpdate : "tool_call_update" ,
264+ ...duplicateRunningToolUpdate ( {
265+ toolCallId : part . callID ,
266+ toolName : part . tool ,
267+ state : part . state ,
268+ } ) ,
269+ } ,
270+ } )
271+ return
272+ }
273+ this . shellSnapshots . set ( part . callID , output )
274+ }
275+
276+ await this . input . connection . sessionUpdate ( {
277+ sessionId,
278+ update : {
279+ sessionUpdate : "tool_call_update" ,
280+ ...runningToolUpdate ( {
281+ toolCallId : part . callID ,
282+ toolName : part . tool ,
283+ state : part . state ,
284+ output,
285+ } ) ,
286+ } ,
287+ } )
288+ }
289+
290+ private async toolStart ( sessionId : string , part : ToolPart ) {
291+ if ( this . toolStarts . has ( part . callID ) ) return
292+ this . toolStarts . add ( part . callID )
293+ await this . input . connection . sessionUpdate ( {
294+ sessionId,
295+ update : {
296+ sessionUpdate : "tool_call" ,
297+ ...pendingToolCall ( {
298+ toolCallId : part . callID ,
299+ toolName : part . tool ,
300+ } ) ,
301+ } ,
302+ } )
303+ }
304+
305+ private clearTool ( toolCallId : string ) {
306+ this . toolStarts . delete ( toolCallId )
307+ this . shellSnapshots . delete ( toolCallId )
308+ }
184309}
185310
186311export * as ACPNextEvent from "./event"
0 commit comments