@@ -20,6 +20,40 @@ const logger = createLogger('CopilotChatStreaming')
2020// reach them. Keyed by streamId, cleaned up when the stream completes.
2121const activeStreams = new Map < string , AbortController > ( )
2222
23+ // Tracks in-flight streams by chatId so that a subsequent request for the
24+ // same chat can wait until the previous stream (and its onComplete / Go-side
25+ // persistence) has fully settled before forwarding to Go.
26+ const pendingChatStreams = new Map < string , { promise : Promise < void > ; resolve : ( ) => void } > ( )
27+
28+ function registerPendingChatStream ( chatId : string ) : void {
29+ let resolve : ( ) => void
30+ const promise = new Promise < void > ( ( r ) => {
31+ resolve = r
32+ } )
33+ pendingChatStreams . set ( chatId , { promise, resolve : resolve ! } )
34+ }
35+
36+ function resolvePendingChatStream ( chatId : string ) : void {
37+ const entry = pendingChatStreams . get ( chatId )
38+ if ( entry ) {
39+ entry . resolve ( )
40+ pendingChatStreams . delete ( chatId )
41+ }
42+ }
43+
44+ /**
45+ * Wait for any in-flight stream on `chatId` to finish before proceeding.
46+ * Returns immediately if no stream is active. Gives up after `timeoutMs`.
47+ */
48+ export async function waitForPendingChatStream (
49+ chatId : string ,
50+ timeoutMs = 5_000
51+ ) : Promise < void > {
52+ const entry = pendingChatStreams . get ( chatId )
53+ if ( ! entry ) return
54+ await Promise . race ( [ entry . promise , new Promise < void > ( ( r ) => setTimeout ( r , timeoutMs ) ) ] )
55+ }
56+
2357export function abortActiveStream ( streamId : string ) : boolean {
2458 const controller = activeStreams . get ( streamId )
2559 if ( ! controller ) return false
@@ -112,6 +146,10 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS
112146 const abortController = new AbortController ( )
113147 activeStreams . set ( streamId , abortController )
114148
149+ if ( chatId ) {
150+ registerPendingChatStream ( chatId )
151+ }
152+
115153 return new ReadableStream ( {
116154 async start ( controller ) {
117155 const encoder = new TextEncoder ( )
@@ -210,6 +248,9 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS
210248 } )
211249 } finally {
212250 activeStreams . delete ( streamId )
251+ if ( chatId ) {
252+ resolvePendingChatStream ( chatId )
253+ }
213254 try {
214255 controller . close ( )
215256 } catch {
@@ -219,6 +260,7 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS
219260 } ,
220261 cancel ( ) {
221262 clientDisconnected = true
263+ abortController . abort ( )
222264 if ( eventWriter ) {
223265 eventWriter . flush ( ) . catch ( ( ) => { } )
224266 }
0 commit comments