11import { afterEach , beforeEach , describe , expect , it , vi } from "@effect/vitest" ;
22import { MAX_SSE_AGE_MS , McpAgent } from "agents/mcp" ;
3+ import { Effect , Option , Schema } from "effect" ;
34
45const KEEPALIVE_INTERVAL_MS = 25_000 ;
56const MAX_PENDING_SSE_BYTES = 8 * 1024 * 1024 ;
@@ -27,6 +28,18 @@ type RotationLog = {
2728} ;
2829
2930const encoder = new TextEncoder ( ) ;
31+ const RotationLogEvent = Schema . Struct ( {
32+ ageMs : Schema . Number ,
33+ event : Schema . Literal ( "sse_max_age_close" ) ,
34+ pendingBytes : Schema . Number ,
35+ sessionId : Schema . String ,
36+ variant : Schema . Union ( [
37+ Schema . Literal ( "streamable-get" ) ,
38+ Schema . Literal ( "streamable-post" ) ,
39+ Schema . Literal ( "legacy-sse" ) ,
40+ ] ) ,
41+ } ) ;
42+ const decodeRotationLogEvent = Schema . decodeUnknownOption ( Schema . fromJsonString ( RotationLogEvent ) ) ;
3043
3144const flushMicrotasks = async ( ) : Promise < void > => {
3245 await Promise . resolve ( ) ;
@@ -39,18 +52,22 @@ const waitFor = async (predicate: () => boolean): Promise<void> => {
3952 if ( predicate ( ) ) return ;
4053 await flushMicrotasks ( ) ;
4154 }
42- throw new Error ( "Timed out waiting for test condition" ) ;
55+ expect ( predicate ( ) ) . toBe ( true ) ;
4356} ;
4457
4558const drainResponse = ( response : Response ) : Promise < void > => {
46- return (
47- response . body
48- ?. pipeTo (
49- new WritableStream < Uint8Array > ( {
50- write : ( ) => { } ,
51- } ) ,
52- )
53- . catch ( ( ) => { } ) ?? Promise . resolve ( )
59+ return Effect . runPromise (
60+ Effect . ignore (
61+ Effect . tryPromise ( {
62+ try : ( ) =>
63+ response . body ?. pipeTo (
64+ new WritableStream < Uint8Array > ( {
65+ write : ( ) => { } ,
66+ } ) ,
67+ ) ?? Promise . resolve ( ) ,
68+ catch : ( ) => undefined ,
69+ } ) ,
70+ ) ,
5471 ) ;
5572} ;
5673
@@ -91,11 +108,11 @@ const installStallingTransformStream = () => {
91108 } ;
92109} ;
93110
94- const makeExecutionContext = ( ) : ExecutionContext =>
95- ( {
96- passThroughOnException : ( ) => { } ,
97- waitUntil : ( ) => { } ,
98- } ) as unknown as ExecutionContext ;
111+ const makeExecutionContext = ( ) : ExecutionContext => ( {
112+ passThroughOnException : ( ) => { } ,
113+ props : undefined ,
114+ waitUntil : ( ) => { } ,
115+ } ) ;
99116
100117const makeWebSocket = ( ) : FakeWebSocket => {
101118 const ws = new EventTarget ( ) as FakeWebSocket ;
@@ -209,28 +226,10 @@ const emitClose = (ws: FakeWebSocket): void => {
209226} ;
210227
211228const rotationLogs = ( logs : ReadonlyArray < string > ) : ReadonlyArray < RotationLog > =>
212- logs
213- . map ( ( line ) => {
214- try {
215- return JSON . parse ( line ) as {
216- readonly event ?: string ;
217- readonly sessionId ?: string ;
218- readonly variant ?: string ;
219- readonly ageMs ?: number ;
220- readonly pendingBytes ?: number ;
221- } ;
222- } catch {
223- return null ;
224- }
225- } )
226- . filter (
227- ( event ) : event is RotationLog =>
228- event ?. event === "sse_max_age_close" &&
229- typeof event . sessionId === "string" &&
230- typeof event . variant === "string" &&
231- typeof event . ageMs === "number" &&
232- typeof event . pendingBytes === "number" ,
233- ) ;
229+ logs . flatMap ( ( line ) => {
230+ const decoded = decodeRotationLogEvent ( line ) ;
231+ return Option . isSome ( decoded ) ? [ decoded . value ] : [ ] ;
232+ } ) ;
234233
235234describe ( "agents SSE max-age rotation" , ( ) => {
236235 let errorLogs : string [ ] = [ ] ;
0 commit comments