@@ -30,6 +30,8 @@ const DEFAULT_STREAM_KEY = "chat";
3030const DEFAULT_BASE_URL = "https://api.trigger.dev" ;
3131const DEFAULT_STREAM_TIMEOUT_SECONDS = 120 ;
3232
33+
34+
3335/**
3436 * Options for creating a TriggerChatTransport.
3537 */
@@ -91,6 +93,8 @@ type ChatSessionState = {
9193 publicAccessToken : string ;
9294 /** Last SSE event ID — used to resume the stream without replaying old events. */
9395 lastEventId ?: string ;
96+ /** Set when the stream was aborted mid-turn (stop). On reconnect, skip chunks until __trigger_turn_complete. */
97+ skipToTurnComplete ?: boolean ;
9498} ;
9599
96100/**
@@ -164,14 +168,12 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
164168 } ;
165169
166170 const session = this . sessions . get ( chatId ) ;
167-
168171 // If we have an existing run, send the message via input stream
169172 // to resume the conversation in the same run.
170173 if ( session ?. runId ) {
171174 try {
172175 const apiClient = new ApiClient ( this . baseURL , session . publicAccessToken ) ;
173176 await apiClient . sendInputStream ( session . runId , CHAT_MESSAGES_STREAM_ID , payload ) ;
174-
175177 return this . subscribeToStream (
176178 session . runId ,
177179 session . publicAccessToken ,
@@ -205,7 +207,6 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
205207 runId,
206208 publicAccessToken : publicAccessToken ?? currentToken ,
207209 } ) ;
208-
209210 return this . subscribeToStream (
210211 runId ,
211212 publicAccessToken ?? currentToken ,
@@ -256,7 +257,8 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
256257 abortSignal . addEventListener (
257258 "abort" ,
258259 ( ) => {
259- if ( session ?. runId ) {
260+ if ( session ) {
261+ session . skipToTurnComplete = true ;
260262 const api = new ApiClient ( this . baseURL , session . publicAccessToken ) ;
261263 api
262264 . sendInputStream ( session . runId , CHAT_STOP_STREAM_ID , { stop : true } )
@@ -283,16 +285,18 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
283285 try {
284286 const sseStream = await subscription . subscribe ( ) ;
285287 const reader = sseStream . getReader ( ) ;
288+ let chunkCount = 0 ;
286289
287290 try {
288291 while ( true ) {
289292 const { done, value } = await reader . read ( ) ;
290293
291294 if ( done ) {
292- // Stream closed without a control chunk — the run has
293- // ended (or was killed). Clear the session so that the
294- // next message triggers a fresh run.
295- if ( chatId ) {
295+ // Only delete session if the stream ended naturally (not aborted by stop).
296+ // When the user clicks stop, the abort closes the SSE reader which
297+ // returns done=true, but the run is still alive and waiting for
298+ // the next message via input streams.
299+ if ( chatId && ! combinedSignal . aborted ) {
296300 this . sessions . delete ( chatId ) ;
297301 }
298302 controller . close ( ) ;
@@ -315,11 +319,17 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
315319 if ( value . chunk != null && typeof value . chunk === "object" ) {
316320 const chunk = value . chunk as Record < string , unknown > ;
317321
318- // Intercept the turn-complete control chunk emitted by
319- // `chatTask` after the AI response stream completes. This
320- // chunk is never forwarded to the AI SDK consumer.
322+ // After a stop, skip leftover chunks from the stopped turn
323+ // until we see the __trigger_turn_complete marker.
324+ if ( session ?. skipToTurnComplete ) {
325+ if ( chunk . type === "__trigger_turn_complete" ) {
326+ session . skipToTurnComplete = false ;
327+ chunkCount = 0 ;
328+ }
329+ continue ;
330+ }
331+
321332 if ( chunk . type === "__trigger_turn_complete" && chatId ) {
322- // Abort the underlying fetch to close the SSE connection
323333 internalAbort . abort ( ) ;
324334 try {
325335 controller . close ( ) ;
@@ -329,6 +339,7 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
329339 return ;
330340 }
331341
342+ chunkCount ++ ;
332343 controller . enqueue ( chunk as unknown as UIMessageChunk ) ;
333344 }
334345 }
0 commit comments