@@ -290,6 +290,114 @@ it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => {
290290 } ) ,
291291 ) ;
292292
293+ it . effect ( "routes a large notification fragmented across thousands of input chunks" , ( ) =>
294+ Effect . gen ( function * ( ) {
295+ const { stdio, input, output } = yield * makeInMemoryStdio ( ) ;
296+ const notifications : Array < CodexProtocol . CodexAppServerIncomingNotification > = [ ] ;
297+ const transport = yield * CodexProtocol . makeCodexAppServerPatchedProtocol ( {
298+ stdio,
299+ onNotification : ( notification ) =>
300+ Effect . sync ( ( ) => {
301+ notifications . push ( notification ) ;
302+ } ) ,
303+ } ) ;
304+ const response = yield * transport . request ( "thread/read" , { } ) . pipe ( Effect . forkScoped ) ;
305+ yield * Queue . take ( output ) ;
306+
307+ const notification = {
308+ method : "turn/diff/updated" ,
309+ params : {
310+ threadId : "thread-1" ,
311+ turnId : "turn-1" ,
312+ diff : "x" . repeat ( 4 * 1024 * 1024 ) ,
313+ } ,
314+ } ;
315+ const bytes = encoder . encode (
316+ `${ encodeUnknownJsonString ( notification ) } \n${ encodeUnknownJsonString ( { id : 1 , result : { ok : true } } ) } \n` ,
317+ ) ;
318+ for ( let offset = 0 ; offset < bytes . length ; offset += 1024 ) {
319+ yield * Queue . offer ( input , bytes . subarray ( offset , offset + 1024 ) ) ;
320+ }
321+
322+ assert . deepEqual ( yield * Fiber . join ( response ) , { ok : true } ) ;
323+ assert . deepEqual ( notifications , [ notification ] ) ;
324+ } ) ,
325+ ) ;
326+
327+ it . effect . each ( [ 1 , 7 , 1024 ] ) (
328+ "preserves JSONL framing and UTF-8 across %i-byte input chunks" ,
329+ ( chunkSize ) =>
330+ Effect . gen ( function * ( ) {
331+ const { stdio, input } = yield * makeInMemoryStdio ( ) ;
332+ const notifications : Array < CodexProtocol . CodexAppServerIncomingNotification > = [ ] ;
333+ const rawLines : Array < unknown > = [ ] ;
334+ const termination = yield * Deferred . make < CodexError . CodexAppServerError > ( ) ;
335+ yield * CodexProtocol . makeCodexAppServerPatchedProtocol ( {
336+ stdio,
337+ logIncoming : true ,
338+ logger : ( event ) =>
339+ Effect . sync ( ( ) => {
340+ if ( event . stage === "raw" ) {
341+ rawLines . push ( event . payload ) ;
342+ }
343+ } ) ,
344+ onNotification : ( notification ) =>
345+ Effect . sync ( ( ) => {
346+ notifications . push ( notification ) ;
347+ } ) ,
348+ onTermination : ( error ) => Deferred . succeed ( termination , error ) . pipe ( Effect . asVoid ) ,
349+ } ) ;
350+
351+ const firstLine = '{"method":"x/first",\r"params":{"text":"hé🙂"}}' ;
352+ const secondLine = '{"method":"x/second","params":{"value":2}}' ;
353+ const finalLine = '{"method":"x/final","params":{"text":"最後"}}\r' ;
354+ const bytes = encoder . encode ( `\n \t\r\n${ firstLine } \r\n\n${ secondLine } \n${ finalLine } ` ) ;
355+ for ( let offset = 0 ; offset < bytes . length ; offset += chunkSize ) {
356+ yield * Queue . offer ( input , bytes . subarray ( offset , offset + chunkSize ) ) ;
357+ }
358+ yield * Queue . end ( input ) ;
359+
360+ assert . instanceOf (
361+ yield * Deferred . await ( termination ) ,
362+ CodexError . CodexAppServerInputStreamEndedError ,
363+ ) ;
364+ assert . deepEqual ( notifications , [
365+ { method : "x/first" , params : { text : "hé🙂" } } ,
366+ { method : "x/second" , params : { value : 2 } } ,
367+ { method : "x/final" , params : { text : "最後" } } ,
368+ ] ) ;
369+ assert . deepEqual ( rawLines , [ firstLine , secondLine , finalLine ] ) ;
370+ } ) ,
371+ ) ;
372+
373+ it . effect ( "reports a malformed fragmented final line before input stream termination" , ( ) =>
374+ Effect . gen ( function * ( ) {
375+ const { stdio, input, output } = yield * makeInMemoryStdio ( ) ;
376+ const termination = yield * Deferred . make < CodexError . CodexAppServerError > ( ) ;
377+ const transport = yield * CodexProtocol . makeCodexAppServerPatchedProtocol ( {
378+ stdio,
379+ onTermination : ( error ) => Deferred . succeed ( termination , error ) . pipe ( Effect . asVoid ) ,
380+ } ) ;
381+ const response = yield * transport . request ( "thread/read" , { } ) . pipe ( Effect . forkScoped ) ;
382+ yield * Queue . take ( output ) ;
383+
384+ yield * Queue . offer ( input , encoder . encode ( '{"id":1,' ) ) ;
385+ yield * Queue . offer ( input , encoder . encode ( '"result":' ) ) ;
386+ yield * Queue . end ( input ) ;
387+
388+ const error = yield * Deferred . await ( termination ) ;
389+ assert . instanceOf ( error , CodexError . CodexAppServerProtocolParseError ) ;
390+ assert . equal ( error . operation , "decode-wire-message" ) ;
391+ const responseError = yield * Fiber . join ( response ) . pipe (
392+ Effect . match ( {
393+ onFailure : ( failure ) => failure ,
394+ onSuccess : ( ) => assert . fail ( "Expected the malformed response to fail the request" ) ,
395+ } ) ,
396+ ) ;
397+ assert . strictEqual ( responseError , error ) ;
398+ } ) ,
399+ ) ;
400+
293401 it . effect ( "surfaces JSON encoding failures as protocol parse errors" , ( ) =>
294402 Effect . gen ( function * ( ) {
295403 const { stdio } = yield * makeInMemoryStdio ( ) ;
0 commit comments