@@ -536,4 +536,226 @@ describe("Connection", () => {
536536 expect ( response . authMethods ) . toHaveLength ( 1 ) ;
537537 expect ( response . authMethods ?. [ 0 ] . id ) . toBe ( "oauth" ) ;
538538 } ) ;
539+
540+ it ( "handles extension methods and notifications" , async ( ) => {
541+ const extensionLog : string [ ] = [ ] ;
542+
543+ // Create client with extension method support
544+ class TestClient implements Client {
545+ async writeTextFile (
546+ _ : WriteTextFileRequest ,
547+ ) : Promise < WriteTextFileResponse > {
548+ return null ;
549+ }
550+ async readTextFile (
551+ _ : ReadTextFileRequest ,
552+ ) : Promise < ReadTextFileResponse > {
553+ return { content : "test" } ;
554+ }
555+ async requestPermission (
556+ _ : RequestPermissionRequest ,
557+ ) : Promise < RequestPermissionResponse > {
558+ return {
559+ outcome : {
560+ outcome : "selected" ,
561+ optionId : "allow" ,
562+ } ,
563+ } ;
564+ }
565+ async sessionUpdate ( _ : SessionNotification ) : Promise < void > {
566+ // no-op
567+ }
568+ async extMethod (
569+ method : string ,
570+ params : Record < string , unknown > ,
571+ ) : Promise < Record < string , unknown > > {
572+ if ( method === "example.com/ping" ) {
573+ return { response : "pong" , params } ;
574+ }
575+ throw new Error ( `Unknown method: ${ method } ` ) ;
576+ }
577+ async extNotification (
578+ method : string ,
579+ params : Record < string , unknown > ,
580+ ) : Promise < void > {
581+ extensionLog . push ( `client extNotification: ${ method } ` ) ;
582+ }
583+ }
584+
585+ // Create agent with extension method support
586+ class TestAgent implements Agent {
587+ async initialize ( _ : InitializeRequest ) : Promise < InitializeResponse > {
588+ return {
589+ protocolVersion : PROTOCOL_VERSION ,
590+ agentCapabilities : { loadSession : false } ,
591+ } ;
592+ }
593+ async newSession ( _ : NewSessionRequest ) : Promise < NewSessionResponse > {
594+ return { sessionId : "test-session" } ;
595+ }
596+ async authenticate ( _ : AuthenticateRequest ) : Promise < void > {
597+ // no-op
598+ }
599+ async prompt ( _ : PromptRequest ) : Promise < PromptResponse > {
600+ return { stopReason : "end_turn" } ;
601+ }
602+ async cancel ( _ : CancelNotification ) : Promise < void > {
603+ // no-op
604+ }
605+ async extMethod (
606+ method : string ,
607+ params : Record < string , unknown > ,
608+ ) : Promise < Record < string , unknown > > {
609+ if ( method === "example.com/echo" ) {
610+ return { echo : params } ;
611+ }
612+ throw new Error ( `Unknown method: ${ method } ` ) ;
613+ }
614+ async extNotification (
615+ method : string ,
616+ params : Record < string , unknown > ,
617+ ) : Promise < void > {
618+ extensionLog . push ( `agent extNotification: ${ method } ` ) ;
619+ }
620+ }
621+
622+ const agentConnection = new ClientSideConnection (
623+ ( ) => new TestClient ( ) ,
624+ clientToAgent . writable ,
625+ agentToClient . readable ,
626+ ) ;
627+
628+ const clientConnection = new AgentSideConnection (
629+ ( ) => new TestAgent ( ) ,
630+ agentToClient . writable ,
631+ clientToAgent . readable ,
632+ ) ;
633+
634+ // Test agent calling client extension method
635+ const clientResponse = await clientConnection . extMethod (
636+ "example.com/ping" ,
637+ {
638+ data : "test" ,
639+ } ,
640+ ) ;
641+ expect ( clientResponse ) . toEqual ( {
642+ response : "pong" ,
643+ params : { data : "test" } ,
644+ } ) ;
645+
646+ // Test client calling agent extension method
647+ const agentResponse = await agentConnection . extMethod ( "example.com/echo" , {
648+ message : "hello" ,
649+ } ) ;
650+ expect ( agentResponse ) . toEqual ( { echo : { message : "hello" } } ) ;
651+
652+ // Test extension notifications
653+ await clientConnection . extNotification ( "example.com/client/notify" , {
654+ info : "client notification" ,
655+ } ) ;
656+ await agentConnection . extNotification ( "example.com/agent/notify" , {
657+ info : "agent notification" ,
658+ } ) ;
659+
660+ // Wait a bit for async handlers
661+ await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
662+
663+ // Verify notifications were logged
664+ expect ( extensionLog ) . toContain (
665+ "client extNotification: example.com/client/notify" ,
666+ ) ;
667+ expect ( extensionLog ) . toContain (
668+ "agent extNotification: example.com/agent/notify" ,
669+ ) ;
670+ } ) ;
671+
672+ it ( "handles optional extension methods correctly" , async ( ) => {
673+ // Create client WITHOUT extension methods
674+ class TestClientWithoutExtensions implements Client {
675+ async writeTextFile (
676+ _ : WriteTextFileRequest ,
677+ ) : Promise < WriteTextFileResponse > {
678+ return null ;
679+ }
680+ async readTextFile (
681+ _ : ReadTextFileRequest ,
682+ ) : Promise < ReadTextFileResponse > {
683+ return { content : "test" } ;
684+ }
685+ async requestPermission (
686+ _ : RequestPermissionRequest ,
687+ ) : Promise < RequestPermissionResponse > {
688+ return {
689+ outcome : {
690+ outcome : "selected" ,
691+ optionId : "allow" ,
692+ } ,
693+ } ;
694+ }
695+ async sessionUpdate ( _ : SessionNotification ) : Promise < void > {
696+ // no-op
697+ }
698+ // Note: No extMethod or extNotification implemented
699+ }
700+
701+ // Create agent WITHOUT extension methods
702+ class TestAgentWithoutExtensions implements Agent {
703+ async initialize ( _ : InitializeRequest ) : Promise < InitializeResponse > {
704+ return {
705+ protocolVersion : PROTOCOL_VERSION ,
706+ agentCapabilities : { loadSession : false } ,
707+ } ;
708+ }
709+ async newSession ( _ : NewSessionRequest ) : Promise < NewSessionResponse > {
710+ return { sessionId : "test-session" } ;
711+ }
712+ async authenticate ( _ : AuthenticateRequest ) : Promise < void > {
713+ // no-op
714+ }
715+ async prompt ( _ : PromptRequest ) : Promise < PromptResponse > {
716+ return { stopReason : "end_turn" } ;
717+ }
718+ async cancel ( _ : CancelNotification ) : Promise < void > {
719+ // no-op
720+ }
721+ // Note: No extMethod or extNotification implemented
722+ }
723+
724+ const agentConnection = new ClientSideConnection (
725+ ( ) => new TestClientWithoutExtensions ( ) ,
726+ clientToAgent . writable ,
727+ agentToClient . readable ,
728+ ) ;
729+
730+ const clientConnection = new AgentSideConnection (
731+ ( ) => new TestAgentWithoutExtensions ( ) ,
732+ agentToClient . writable ,
733+ clientToAgent . readable ,
734+ ) ;
735+
736+ // Test that calling extension methods on connections without them throws method not found
737+ try {
738+ await clientConnection . extMethod ( "example.com/ping" , { data : "test" } ) ;
739+ expect . fail ( "Should have thrown method not found error" ) ;
740+ } catch ( error : any ) {
741+ expect ( error . code ) . toBe ( - 32601 ) ; // Method not found
742+ expect ( error . data . method ) . toBe ( "example.com/ping" ) ; // Should show inner method name
743+ }
744+
745+ try {
746+ await agentConnection . extMethod ( "example.com/echo" , { message : "hello" } ) ;
747+ expect . fail ( "Should have thrown method not found error" ) ;
748+ } catch ( error : any ) {
749+ expect ( error . code ) . toBe ( - 32601 ) ; // Method not found
750+ expect ( error . data . method ) . toBe ( "example.com/echo" ) ; // Should show inner method name
751+ }
752+
753+ // Notifications should be ignored when not implemented (no error thrown)
754+ await clientConnection . extNotification ( "example.com/notify" , {
755+ info : "test" ,
756+ } ) ;
757+ await agentConnection . extNotification ( "example.com/notify" , {
758+ info : "test" ,
759+ } ) ;
760+ } ) ;
539761} ) ;
0 commit comments