@@ -2310,6 +2310,70 @@ describe("AwsBedrockHandler", () => {
23102310 clearTimeoutSpy . mockRestore ( )
23112311 clearTimeout ( timeoutHandle )
23122312 } )
2313+
2314+ it ( "createMessage should abort the in-flight request when the 10 minute request timeout fires" , async ( ) => {
2315+ const handler = new AwsBedrockHandler ( {
2316+ apiModelId : "anthropic.claude-3-5-sonnet-20241022-v2:0" ,
2317+ awsAccessKey : "test-access-key" ,
2318+ awsSecretKey : "test-secret-key" ,
2319+ awsRegion : "us-east-1" ,
2320+ } )
2321+
2322+ let internalSignal : AbortSignal | undefined
2323+ const mockSend = vi
2324+ . fn ( )
2325+ . mockImplementation ( ( _command : unknown , options ?: { abortSignal ?: AbortSignal } ) => {
2326+ internalSignal = options ?. abortSignal
2327+ return new Promise < unknown > ( ( _resolve , reject ) => {
2328+ internalSignal ?. addEventListener (
2329+ "abort" ,
2330+ ( ) => {
2331+ const abortError = new Error ( "The operation was aborted" )
2332+ abortError . name = "AbortError"
2333+ reject ( abortError )
2334+ } ,
2335+ { once : true } ,
2336+ )
2337+ } )
2338+ } )
2339+ handler [ "client" ] . send = mockSend
2340+
2341+ // Capture the 10-minute request timer scheduled by createMessage
2342+ const setTimeoutSpy = vi . spyOn ( globalThis , "setTimeout" )
2343+
2344+ const generator = handler . createMessage (
2345+ "You are a helpful assistant" ,
2346+ [ { role : "user" , content : "Hello" } ] ,
2347+ makeCreateMessageMetadata ( ) ,
2348+ )
2349+ const consumed = ( async ( ) => {
2350+ for await ( const _chunk of generator ) {
2351+ // ignore chunks
2352+ }
2353+ } ) ( )
2354+
2355+ // Wait until the request is in flight and the 10-minute timer is scheduled
2356+ let timeoutHandle : NodeJS . Timeout | undefined
2357+ let timeoutCallback : ( ( ) => void ) | undefined
2358+ await vi . waitFor ( ( ) => {
2359+ const timerIndex = setTimeoutSpy . mock . calls . findIndex ( ( [ , delay ] ) => delay === 10 * 60 * 1000 )
2360+ expect ( timerIndex ) . toBeGreaterThanOrEqual ( 0 )
2361+ expect ( internalSignal ) . toBeDefined ( )
2362+ timeoutHandle = setTimeoutSpy . mock . results [ timerIndex ] ?. value as NodeJS . Timeout
2363+ timeoutCallback = setTimeoutSpy . mock . calls [ timerIndex ] ?. [ 0 ] as ( ) => void
2364+ } )
2365+ expect ( internalSignal ?. aborted ) . toBe ( false )
2366+
2367+ // Fire the 10-minute request timeout: it must abort the request-local
2368+ // controller, which cancels the in-flight request with an AbortError.
2369+ timeoutCallback ?.( )
2370+
2371+ await expect ( consumed ) . rejects . toMatchObject ( { name : "AbortError" } )
2372+
2373+ // Belt and braces: make sure no real 10-minute timer survives the test
2374+ setTimeoutSpy . mockRestore ( )
2375+ clearTimeout ( timeoutHandle )
2376+ } )
23132377 } )
23142378 } )
23152379} )
0 commit comments