@@ -137,6 +137,82 @@ describe("createHttpStream", () => {
137137 }
138138 } ) ;
139139
140+ it ( "propagates cookies across initialize, SSE, session POST, and DELETE" , async ( ) => {
141+ const controlledFetch = createControlledFetch ( {
142+ initializeCookies : [ "transport=alpha; Path=/" ] ,
143+ getCookies : [ "route=bravo; Path=/" ] ,
144+ } ) ;
145+ const stream = createHttpStream ( "https://agent.example/acp" , {
146+ fetch : controlledFetch . fetch ,
147+ headers : {
148+ Cookie : "caller=custom; transport=caller" ,
149+ } ,
150+ } ) ;
151+ const writer = stream . writable . getWriter ( ) ;
152+ const reader = stream . readable . getReader ( ) ;
153+
154+ try {
155+ await writer . write ( initializeRequest ) ;
156+ await readMessage ( reader ) ;
157+ await controlledFetch . sendSse ( 0 , sessionNewResponse ) ;
158+ await readMessage ( reader ) ;
159+ await writer . write ( promptRequest ) ;
160+ await writer . close ( ) ;
161+
162+ expect ( requestAt ( controlledFetch . requests , 0 ) . credentials ) . toBe (
163+ "include" ,
164+ ) ;
165+ expect ( requestAt ( controlledFetch . requests , 0 ) . headers . get ( "Cookie" ) ) . toBe (
166+ "caller=custom; transport=caller" ,
167+ ) ;
168+ expect ( requestAt ( controlledFetch . requests , 1 ) . headers . get ( "Cookie" ) ) . toBe (
169+ "transport=caller; caller=custom" ,
170+ ) ;
171+ expect ( requestAt ( controlledFetch . requests , 2 ) . headers . get ( "Cookie" ) ) . toBe (
172+ "transport=caller; route=bravo; caller=custom" ,
173+ ) ;
174+ expect ( requestAt ( controlledFetch . requests , 3 ) . headers . get ( "Cookie" ) ) . toBe (
175+ "transport=caller; route=bravo; caller=custom" ,
176+ ) ;
177+ expect ( requestAt ( controlledFetch . requests , 4 ) . headers . get ( "Cookie" ) ) . toBe (
178+ "transport=caller; route=bravo; caller=custom" ,
179+ ) ;
180+ expect (
181+ controlledFetch . requests . map ( ( request ) => request . credentials ) ,
182+ ) . toEqual ( [ "include" , "include" , "include" , "include" , "include" ] ) ;
183+ } finally {
184+ reader . releaseLock ( ) ;
185+ writer . releaseLock ( ) ;
186+ }
187+ } ) ;
188+
189+ it ( "omits managed cookies when cookie handling is disabled" , async ( ) => {
190+ const controlledFetch = createControlledFetch ( {
191+ initializeCookies : [ "transport=alpha; Path=/" ] ,
192+ } ) ;
193+ const stream = createHttpStream ( "https://agent.example/acp" , {
194+ fetch : controlledFetch . fetch ,
195+ cookies : "omit" ,
196+ } ) ;
197+ const writer = stream . writable . getWriter ( ) ;
198+ const reader = stream . readable . getReader ( ) ;
199+
200+ try {
201+ await writer . write ( initializeRequest ) ;
202+ await readMessage ( reader ) ;
203+
204+ expect ( requestAt ( controlledFetch . requests , 0 ) . credentials ) . toBe ( "omit" ) ;
205+ expect ( requestAt ( controlledFetch . requests , 1 ) . credentials ) . toBe ( "omit" ) ;
206+ expect (
207+ requestAt ( controlledFetch . requests , 1 ) . headers . get ( "Cookie" ) ,
208+ ) . toBeNull ( ) ;
209+ } finally {
210+ reader . releaseLock ( ) ;
211+ writer . releaseLock ( ) ;
212+ await stream . writable . close ( ) ;
213+ }
214+ } ) ;
215+
140216 it ( "sends DELETE and aborts SSE requests when closed" , async ( ) => {
141217 const controlledFetch = createControlledFetch ( ) ;
142218 const stream = createHttpStream ( "https://agent.example/acp" , {
@@ -330,6 +406,7 @@ interface RecordedRequest {
330406 readonly method : string ;
331407 readonly headers : Headers ;
332408 readonly body : string ;
409+ readonly credentials : RequestCredentials | undefined ;
333410}
334411
335412interface RecordedSseRequest {
@@ -349,7 +426,14 @@ interface TestClientState {
349426 readonly permissionRequests ?: RequestPermissionRequest [ ] ;
350427}
351428
352- function createControlledFetch ( ) : ControlledFetch {
429+ interface ControlledFetchOptions {
430+ readonly initializeCookies ?: readonly string [ ] ;
431+ readonly getCookies ?: readonly string [ ] ;
432+ }
433+
434+ function createControlledFetch (
435+ options : ControlledFetchOptions = { } ,
436+ ) : ControlledFetch {
353437 const requests : RecordedRequest [ ] = [ ] ;
354438 const sseRequests : RecordedSseRequest [ ] = [ ] ;
355439 const encoder = new TextEncoder ( ) ;
@@ -365,11 +449,13 @@ function createControlledFetch(): ControlledFetch {
365449 method,
366450 headers,
367451 body : bodyToString ( init ?. body ) ,
452+ credentials : init ?. credentials ,
368453 } ) ;
369454
370455 if ( method === "POST" && ! headers . has ( HEADER_CONNECTION_ID ) ) {
371456 return jsonResponse ( initializeResponse , 200 , {
372457 [ HEADER_CONNECTION_ID ] : "connection-1" ,
458+ ...setCookieResponseHeaders ( options . initializeCookies ) ,
373459 } ) ;
374460 }
375461
@@ -395,7 +481,10 @@ function createControlledFetch(): ControlledFetch {
395481
396482 return new Response ( stream . readable , {
397483 status : 200 ,
398- headers : { "Content-Type" : EVENT_STREAM_MIME_TYPE } ,
484+ headers : {
485+ "Content-Type" : EVENT_STREAM_MIME_TYPE ,
486+ ...setCookieResponseHeaders ( options . getCookies ) ,
487+ } ,
399488 } ) ;
400489 }
401490
@@ -485,3 +574,9 @@ function jsonResponse(
485574 } ,
486575 } ) ;
487576}
577+
578+ function setCookieResponseHeaders (
579+ cookies : readonly string [ ] | undefined ,
580+ ) : Record < string , string > {
581+ return cookies ? { "Set-Cookie" : cookies . join ( ", " ) } : { } ;
582+ }
0 commit comments