@@ -74,7 +74,7 @@ export class StreamableHttpRunner extends TransportRunnerBase {
74
74
jsonrpc : "2.0" ,
75
75
error : {
76
76
code : JSON_RPC_ERROR_CODE_SESSION_ID_INVALID ,
77
- message : ` session id is invalid` ,
77
+ message : " session id is invalid" ,
78
78
} ,
79
79
} ) ;
80
80
return ;
@@ -85,7 +85,7 @@ export class StreamableHttpRunner extends TransportRunnerBase {
85
85
jsonrpc : "2.0" ,
86
86
error : {
87
87
code : JSON_RPC_ERROR_CODE_SESSION_NOT_FOUND ,
88
- message : ` session not found` ,
88
+ message : " session not found" ,
89
89
} ,
90
90
} ) ;
91
91
return ;
@@ -114,12 +114,48 @@ export class StreamableHttpRunner extends TransportRunnerBase {
114
114
}
115
115
116
116
const server = this . setupServer ( ) ;
117
+ let keepAliveLoop : NodeJS . Timeout ;
117
118
const transport = new StreamableHTTPServerTransport ( {
118
119
sessionIdGenerator : ( ) : string => randomUUID ( ) . toString ( ) ,
119
120
onsessioninitialized : ( sessionId ) : void => {
120
121
server . session . logger . setAttribute ( "sessionId" , sessionId ) ;
121
122
122
123
this . sessionStore . setSession ( sessionId , transport , server . session . logger ) ;
124
+
125
+ let failedPings = 0 ;
126
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
127
+ keepAliveLoop = setInterval ( async ( ) => {
128
+ try {
129
+ this . logger . debug ( {
130
+ id : LogId . streamableHttpTransportKeepAlive ,
131
+ context : "streamableHttpTransport" ,
132
+ message : "Sending ping" ,
133
+ } ) ;
134
+
135
+ await transport . send ( {
136
+ jsonrpc : "2.0" ,
137
+ method : "ping" ,
138
+ } ) ;
139
+ failedPings = 0 ;
140
+ } catch ( err ) {
141
+ try {
142
+ failedPings ++ ;
143
+ this . logger . warning ( {
144
+ id : LogId . streamableHttpTransportKeepAliveFailure ,
145
+ context : "streamableHttpTransport" ,
146
+ message : `Error sending ping (attempt #${ failedPings } ): ${ err instanceof Error ? err . message : String ( err ) } ` ,
147
+ } ) ;
148
+
149
+ if ( failedPings > 3 ) {
150
+ clearInterval ( keepAliveLoop ) ;
151
+ await transport . close ( ) ;
152
+ }
153
+ } catch {
154
+ // Ignore the error of the transport close as there's nothing else
155
+ // we can do at this point.
156
+ }
157
+ }
158
+ } , 30_000 ) ;
123
159
} ,
124
160
onsessionclosed : async ( sessionId ) : Promise < void > => {
125
161
try {
@@ -135,6 +171,8 @@ export class StreamableHttpRunner extends TransportRunnerBase {
135
171
} ) ;
136
172
137
173
transport . onclose = ( ) : void => {
174
+ clearInterval ( keepAliveLoop ) ;
175
+
138
176
server . close ( ) . catch ( ( error ) => {
139
177
this . logger . error ( {
140
178
id : LogId . streamableHttpTransportCloseFailure ,
0 commit comments