@@ -218,7 +218,7 @@ it.effect("effect-acp agent handles core agent requests and outbound client requ
218218 } ) ,
219219) ;
220220
221- it . effect ( "effect-acp agent uses distinct ids for RPC calls and extension requests " , ( ) =>
221+ it . effect ( "effect-acp agent keeps RPC ids within int32 and apart from extension ids " , ( ) =>
222222 Effect . gen ( function * ( ) {
223223 const { stdio, input, output } = yield * makeInMemoryStdio ( ) ;
224224 const scope = yield * Scope . make ( ) ;
@@ -267,6 +267,11 @@ it.effect("effect-acp agent uses distinct ids for RPC calls and extension reques
267267 : yield * decodedExt ( firstOutbound ) ;
268268
269269 assert . notEqual ( permissionRequest . id , extRequest . id ) ;
270+ // Keep locally generated ids compatible with SDKs that decode signed int32.
271+ assert . typeOf ( permissionRequest . id , "number" ) ;
272+ assert . equal ( permissionRequest . id , 2 ** 30 ) ;
273+ assert . isAtMost ( Number ( permissionRequest . id ) , 2 ** 31 - 1 ) ;
274+ assert . equal ( extRequest . id , 1 ) ;
270275
271276 yield * Queue . offer (
272277 input ,
@@ -293,6 +298,91 @@ it.effect("effect-acp agent uses distinct ids for RPC calls and extension reques
293298 const permission = yield * Fiber . join ( permissionFiber ) ;
294299 assert . equal ( permission . outcome . outcome , "selected" ) ;
295300 assert . deepEqual ( yield * Fiber . join ( extFiber ) , { ok : true } ) ;
301+
302+ const nextPermissionFiber = yield * agent . client
303+ . requestPermission ( permissionRequest . params )
304+ . pipe ( Effect . forkScoped ) ;
305+ const nextPermissionRequest = yield * decodedPermission ( yield * Queue . take ( output ) ) ;
306+ assert . equal ( nextPermissionRequest . id , 2 ** 30 + 1 ) ;
307+ yield * Queue . offer (
308+ input ,
309+ yield * encodeJsonl ( RequestPermissionResponse , {
310+ jsonrpc : "2.0" ,
311+ id : nextPermissionRequest . id ,
312+ result : { outcome : { outcome : "cancelled" } } ,
313+ } ) ,
314+ ) ;
315+ assert . equal ( ( yield * Fiber . join ( nextPermissionFiber ) ) . outcome . outcome , "cancelled" ) ;
296316 } ) . pipe ( Effect . provide ( context ) , Effect . ensuring ( Scope . close ( scope , Exit . void ) ) ) ;
297317 } ) ,
298318) ;
319+
320+ it . effect . each ( [ 0 , 2 ** 30 , 2 ** 32 ] ) (
321+ "effect-acp agent answers incoming id %s independently of outbound ids" ,
322+ ( requestId ) =>
323+ Effect . gen ( function * ( ) {
324+ const { stdio, input, output } = yield * makeInMemoryStdio ( ) ;
325+ const agent = yield * AcpAgent . make ( stdio ) ;
326+ const contexts = yield * Ref . make < Array < AcpProtocol . AcpRequestContext > > ( [ ] ) ;
327+ yield * agent . handleInitialize ( ( _request , context ) =>
328+ Ref . update ( contexts , ( current ) => [ ...current , context ] ) . pipe (
329+ Effect . as ( {
330+ protocolVersion : 2 ,
331+ capabilities : { } ,
332+ info : { name : "mock-agent" , version : "0.0.0" } ,
333+ } ) ,
334+ ) ,
335+ ) ;
336+
337+ // Opposite directions may reuse an id even while a request is pending.
338+ const outboundFiber = yield * agent . client
339+ . requestPermission ( {
340+ sessionId : "session-1" ,
341+ title : "Allow mock action" ,
342+ subject : {
343+ type : "tool_call" ,
344+ toolCall : { toolCallId : "tool-1" , title : "Allow mock action" } ,
345+ } ,
346+ options : [ { optionId : "allow" , name : "Allow" , kind : "allow_once" } ] ,
347+ } )
348+ . pipe ( Effect . forkScoped ) ;
349+ const outbound = yield * decodeRequestPermissionRequest ( yield * Queue . take ( output ) ) ;
350+
351+ yield * Queue . offer (
352+ input ,
353+ yield * encodeJsonl ( InitializeRequest , {
354+ jsonrpc : "2.0" ,
355+ id : requestId ,
356+ method : "initialize" ,
357+ params : {
358+ protocolVersion : 2 ,
359+ capabilities : { } ,
360+ info : { name : "test-client" , version : "0.0.0" } ,
361+ } ,
362+ headers : [ ] ,
363+ } ) ,
364+ ) ;
365+ assert . deepEqual ( yield * decodeInitializeResponse ( yield * Queue . take ( output ) ) , {
366+ jsonrpc : "2.0" ,
367+ id : requestId ,
368+ result : {
369+ protocolVersion : 2 ,
370+ capabilities : { } ,
371+ info : { name : "mock-agent" , version : "0.0.0" } ,
372+ } ,
373+ } ) ;
374+ assert . deepEqual ( yield * Ref . get ( contexts ) , [
375+ { requestId : `$t3:jsonrpc:number:${ requestId } ` , method : "initialize" } ,
376+ ] ) ;
377+
378+ yield * Queue . offer (
379+ input ,
380+ yield * encodeJsonl ( RequestPermissionResponse , {
381+ jsonrpc : "2.0" ,
382+ id : outbound . id ,
383+ result : { outcome : { outcome : "cancelled" } } ,
384+ } ) ,
385+ ) ;
386+ assert . equal ( ( yield * Fiber . join ( outboundFiber ) ) . outcome . outcome , "cancelled" ) ;
387+ } ) ,
388+ ) ;
0 commit comments