@@ -196,6 +196,7 @@ import { extractTokenUsage } from "./tools/extract-token-usage.js";
196196import { extractToolCalls } from "./tools/extract-tool-calls.js" ;
197197import { getFinishReasonFromError } from "./tools/get-finish-reason-from-error.js" ;
198198import {
199+ getEnvKeyCount ,
199200 getProviderEnv ,
200201 getServiceTierIneligibleEnvIndices ,
201202 hasServiceTierEligibleEnvCredential ,
@@ -235,6 +236,7 @@ import {
235236 providerRetryKey ,
236237 selectNextProvider ,
237238 shouldRetryAlternateKey ,
239+ shouldRetrySameKey ,
238240 shouldRetryRequest ,
239241} from "./tools/retry-with-fallback.js" ;
240242import {
@@ -6575,6 +6577,7 @@ chat.openapi(completions, async (c) => {
65756577 // --- Retry loop for provider fallback ---
65766578 const routingAttempts : RoutingAttempt [ ] = [ ] ;
65776579 const failedProviderIds = new Set < string > ( ) ;
6580+ let sameKeyRetryCount = 0 ;
65786581 let res : Response | undefined ;
65796582 for (
65806583 let retryAttempt = 0 ;
@@ -6775,8 +6778,23 @@ chat.openapi(completions, async (c) => {
67756778 maxRetries : routingCfg . retry . maxRetries ,
67766779 } ) ;
67776780 const willRetrySameProvider = sameProviderRetryContext !== null ;
6781+ const willRetrySameKey =
6782+ ! willRetrySameProvider &&
6783+ ! willRetryTimeout &&
6784+ shouldRetrySameKey ( {
6785+ usedProvider,
6786+ errorType : "upstream_timeout" ,
6787+ statusCode : 0 ,
6788+ envVarName,
6789+ envKeyCount : getEnvKeyCount ( envVarName ) ,
6790+ hasOtherProvider : (
6791+ routingMetadata ?. providerScores ?? [ ]
6792+ ) . some ( ( s ) => s . providerId !== usedProvider ) ,
6793+ retryCount : sameKeyRetryCount ,
6794+ maxRetries : routingCfg . retry . maxRetries ,
6795+ } ) ;
67786796 const willRetryRequest =
6779- willRetrySameProvider || willRetryTimeout ;
6797+ willRetrySameProvider || willRetryTimeout || willRetrySameKey ;
67806798
67816799 const baseLogEntry = createLogEntry (
67826800 requestId ,
@@ -6874,6 +6892,30 @@ chat.openapi(completions, async (c) => {
68746892 continue ;
68756893 }
68766894
6895+ if ( willRetrySameKey ) {
6896+ sameKeyRetryCount ++ ;
6897+ // Re-add abort listener (removed by catch/finally on the
6898+ // failed attempt) so a client disconnect during the
6899+ // retried upstream call still cancels.
6900+ c . req . raw . signal . addEventListener ( "abort" , onAbort ) ;
6901+ routingAttempts . push (
6902+ buildRoutingAttempt (
6903+ usedProvider ,
6904+ usedInternalModel ,
6905+ 0 ,
6906+ getErrorType ( 0 ) ,
6907+ false ,
6908+ {
6909+ region : usedRegion ,
6910+ apiKeyHash : usedApiKeyHash ,
6911+ logId : attemptLogId ,
6912+ } ,
6913+ ) ,
6914+ ) ;
6915+ retryAttempt -- ;
6916+ continue ;
6917+ }
6918+
68776919 if ( willRetryTimeout ) {
68786920 routingAttempts . push (
68796921 buildRoutingAttempt (
@@ -7113,7 +7155,23 @@ chat.openapi(completions, async (c) => {
71137155 maxRetries : routingCfg . retry . maxRetries ,
71147156 } ) ;
71157157 const willRetrySameProvider = sameProviderRetryContext !== null ;
7116- const willRetryRequest = willRetrySameProvider || willRetryFetch ;
7158+ const willRetrySameKey =
7159+ ! willRetrySameProvider &&
7160+ ! willRetryFetch &&
7161+ shouldRetrySameKey ( {
7162+ usedProvider,
7163+ errorType : "network_error" ,
7164+ statusCode : 0 ,
7165+ envVarName,
7166+ envKeyCount : getEnvKeyCount ( envVarName ) ,
7167+ hasOtherProvider : (
7168+ routingMetadata ?. providerScores ?? [ ]
7169+ ) . some ( ( s ) => s . providerId !== usedProvider ) ,
7170+ retryCount : sameKeyRetryCount ,
7171+ maxRetries : routingCfg . retry . maxRetries ,
7172+ } ) ;
7173+ const willRetryRequest =
7174+ willRetrySameProvider || willRetryFetch || willRetrySameKey ;
71177175
71187176 const baseLogEntry = createLogEntry (
71197177 requestId ,
@@ -7230,6 +7288,30 @@ chat.openapi(completions, async (c) => {
72307288 continue ;
72317289 }
72327290
7291+ if ( willRetrySameKey ) {
7292+ sameKeyRetryCount ++ ;
7293+ // Re-add abort listener (removed by catch/finally on the
7294+ // failed attempt) so a client disconnect during the
7295+ // retried upstream call still cancels.
7296+ c . req . raw . signal . addEventListener ( "abort" , onAbort ) ;
7297+ routingAttempts . push (
7298+ buildRoutingAttempt (
7299+ usedProvider ,
7300+ usedInternalModel ,
7301+ 0 ,
7302+ getErrorType ( 0 ) ,
7303+ false ,
7304+ {
7305+ region : usedRegion ,
7306+ apiKeyHash : usedApiKeyHash ,
7307+ logId : attemptLogId ,
7308+ } ,
7309+ ) ,
7310+ ) ;
7311+ retryAttempt -- ;
7312+ continue ;
7313+ }
7314+
72337315 if ( willRetryFetch ) {
72347316 routingAttempts . push (
72357317 buildRoutingAttempt (
@@ -7355,8 +7437,23 @@ chat.openapi(completions, async (c) => {
73557437 maxRetries : routingCfg . retry . maxRetries ,
73567438 } ) ;
73577439 const willRetrySameProvider = sameProviderRetryContext !== null ;
7440+ const willRetrySameKey =
7441+ ! willRetrySameProvider &&
7442+ ! willRetryHttpError &&
7443+ shouldRetrySameKey ( {
7444+ usedProvider,
7445+ errorType : finishReason ,
7446+ statusCode : res . status ,
7447+ envVarName,
7448+ envKeyCount : getEnvKeyCount ( envVarName ) ,
7449+ hasOtherProvider : ( routingMetadata ?. providerScores ?? [ ] ) . some (
7450+ ( s ) => s . providerId !== usedProvider ,
7451+ ) ,
7452+ retryCount : sameKeyRetryCount ,
7453+ maxRetries : routingCfg . retry . maxRetries ,
7454+ } ) ;
73587455 const willRetryRequest =
7359- willRetrySameProvider || willRetryHttpError ;
7456+ willRetrySameProvider || willRetryHttpError || willRetrySameKey ;
73607457
73617458 const baseLogEntry = createLogEntry (
73627459 requestId ,
@@ -7514,6 +7611,30 @@ chat.openapi(completions, async (c) => {
75147611 continue ;
75157612 }
75167613
7614+ if ( willRetrySameKey ) {
7615+ sameKeyRetryCount ++ ;
7616+ // Re-add abort listener (removed by catch/finally on the
7617+ // failed attempt) so a client disconnect during the
7618+ // retried upstream call still cancels.
7619+ c . req . raw . signal . addEventListener ( "abort" , onAbort ) ;
7620+ routingAttempts . push (
7621+ buildRoutingAttempt (
7622+ usedProvider ,
7623+ usedInternalModel ,
7624+ res . status ,
7625+ getErrorType ( res . status ) ,
7626+ false ,
7627+ {
7628+ region : usedRegion ,
7629+ apiKeyHash : usedApiKeyHash ,
7630+ logId : attemptLogId ,
7631+ } ,
7632+ ) ,
7633+ ) ;
7634+ retryAttempt -- ;
7635+ continue ;
7636+ }
7637+
75177638 if ( willRetryHttpError ) {
75187639 routingAttempts . push (
75197640 buildRoutingAttempt (
@@ -7675,8 +7796,25 @@ chat.openapi(completions, async (c) => {
76757796 maxRetries : routingCfg . retry . maxRetries ,
76767797 } ) ;
76777798 const willRetrySameProvider = sameProviderRetryContext !== null ;
7799+ const willRetrySameKey =
7800+ ! willRetrySameProvider &&
7801+ ! willRetryStreamingError &&
7802+ shouldRetrySameKey ( {
7803+ usedProvider,
7804+ errorType,
7805+ statusCode : inferredStatusCode ,
7806+ envVarName,
7807+ envKeyCount : getEnvKeyCount ( envVarName ) ,
7808+ hasOtherProvider : ( routingMetadata ?. providerScores ?? [ ] ) . some (
7809+ ( s ) => s . providerId !== usedProvider ,
7810+ ) ,
7811+ retryCount : sameKeyRetryCount ,
7812+ maxRetries : routingCfg . retry . maxRetries ,
7813+ } ) ;
76787814 const willRetryRequest =
7679- willRetrySameProvider || willRetryStreamingError ;
7815+ willRetrySameProvider ||
7816+ willRetryStreamingError ||
7817+ willRetrySameKey ;
76807818
76817819 const baseLogEntry = createLogEntry (
76827820 requestId ,
@@ -7794,6 +7932,30 @@ chat.openapi(completions, async (c) => {
77947932 continue ;
77957933 }
77967934
7935+ if ( willRetrySameKey ) {
7936+ sameKeyRetryCount ++ ;
7937+ // Re-add abort listener (removed by catch/finally on the
7938+ // failed attempt) so a client disconnect during the
7939+ // retried upstream call still cancels.
7940+ c . req . raw . signal . addEventListener ( "abort" , onAbort ) ;
7941+ routingAttempts . push (
7942+ buildRoutingAttempt (
7943+ usedProvider ,
7944+ usedInternalModel ,
7945+ inferredStatusCode ,
7946+ getErrorType ( inferredStatusCode ) ,
7947+ false ,
7948+ {
7949+ region : usedRegion ,
7950+ apiKeyHash : usedApiKeyHash ,
7951+ logId : attemptLogId ,
7952+ } ,
7953+ ) ,
7954+ ) ;
7955+ retryAttempt -- ;
7956+ continue ;
7957+ }
7958+
77977959 if ( willRetryStreamingError ) {
77987960 routingAttempts . push (
77997961 buildRoutingAttempt (
@@ -10666,6 +10828,7 @@ chat.openapi(completions, async (c) => {
1066610828 // --- Retry loop for provider fallback ---
1066710829 const routingAttempts : RoutingAttempt [ ] = [ ] ;
1066810830 const failedProviderIds = new Set < string > ( ) ;
10831+ let sameKeyRetryCount = 0 ;
1066910832 let canceled = false ;
1067010833 let fetchError : Error | null = null ;
1067110834 let isTimeoutFetchError = false ;
@@ -10896,8 +11059,23 @@ chat.openapi(completions, async (c) => {
1089611059 maxRetries : routingCfg . retry . maxRetries ,
1089711060 } ) ;
1089811061 const willRetrySameProvider = sameProviderRetryContext !== null ;
11062+ const willRetrySameKey =
11063+ ! willRetrySameProvider &&
11064+ ! willRetryFetchNonStreaming &&
11065+ shouldRetrySameKey ( {
11066+ usedProvider,
11067+ errorType : "network_error" ,
11068+ statusCode : 0 ,
11069+ envVarName,
11070+ envKeyCount : getEnvKeyCount ( envVarName ) ,
11071+ hasOtherProvider : ( routingMetadata ?. providerScores ?? [ ] ) . some (
11072+ ( s ) => s . providerId !== usedProvider ,
11073+ ) ,
11074+ retryCount : sameKeyRetryCount ,
11075+ maxRetries : routingCfg . retry . maxRetries ,
11076+ } ) ;
1089911077 const willRetryRequest =
10900- willRetrySameProvider || willRetryFetchNonStreaming ;
11078+ willRetrySameProvider || willRetryFetchNonStreaming || willRetrySameKey ;
1090111079
1090211080 const baseLogEntry = createLogEntry (
1090311081 requestId ,
@@ -11015,6 +11193,30 @@ chat.openapi(completions, async (c) => {
1101511193 continue ;
1101611194 }
1101711195
11196+ if ( willRetrySameKey ) {
11197+ sameKeyRetryCount ++ ;
11198+ // Re-add abort listener (removed by the per-attempt finally) so
11199+ // a client disconnect during the retried upstream call still
11200+ // cancels.
11201+ c . req . raw . signal . addEventListener ( "abort" , onAbort ) ;
11202+ routingAttempts . push (
11203+ buildRoutingAttempt (
11204+ usedProvider ,
11205+ usedInternalModel ,
11206+ 0 ,
11207+ getErrorType ( 0 ) ,
11208+ false ,
11209+ {
11210+ region : usedRegion ,
11211+ apiKeyHash : usedApiKeyHash ,
11212+ logId : attemptLogId ,
11213+ } ,
11214+ ) ,
11215+ ) ;
11216+ retryAttempt -- ;
11217+ continue ;
11218+ }
11219+
1101811220 if ( willRetryFetchNonStreaming ) {
1101911221 routingAttempts . push (
1102011222 buildRoutingAttempt (
@@ -11278,8 +11480,23 @@ chat.openapi(completions, async (c) => {
1127811480 maxRetries : routingCfg . retry . maxRetries ,
1127911481 } ) ;
1128011482 const willRetrySameProvider = sameProviderRetryContext !== null ;
11483+ const willRetrySameKey =
11484+ ! willRetrySameProvider &&
11485+ ! willRetryHttpNonStreaming &&
11486+ shouldRetrySameKey ( {
11487+ usedProvider,
11488+ errorType : finishReason ,
11489+ statusCode : res . status ,
11490+ envVarName,
11491+ envKeyCount : getEnvKeyCount ( envVarName ) ,
11492+ hasOtherProvider : ( routingMetadata ?. providerScores ?? [ ] ) . some (
11493+ ( s ) => s . providerId !== usedProvider ,
11494+ ) ,
11495+ retryCount : sameKeyRetryCount ,
11496+ maxRetries : routingCfg . retry . maxRetries ,
11497+ } ) ;
1128111498 const willRetryRequest =
11282- willRetrySameProvider || willRetryHttpNonStreaming ;
11499+ willRetrySameProvider || willRetryHttpNonStreaming || willRetrySameKey ;
1128311500
1128411501 const baseLogEntry = createLogEntry (
1128511502 requestId ,
@@ -11456,6 +11673,30 @@ chat.openapi(completions, async (c) => {
1145611673 continue ;
1145711674 }
1145811675
11676+ if ( willRetrySameKey ) {
11677+ sameKeyRetryCount ++ ;
11678+ // Re-add abort listener (removed by the per-attempt finally) so
11679+ // a client disconnect during the retried upstream call still
11680+ // cancels.
11681+ c . req . raw . signal . addEventListener ( "abort" , onAbort ) ;
11682+ routingAttempts . push (
11683+ buildRoutingAttempt (
11684+ usedProvider ,
11685+ usedInternalModel ,
11686+ res . status ,
11687+ getErrorType ( res . status ) ,
11688+ false ,
11689+ {
11690+ region : usedRegion ,
11691+ apiKeyHash : usedApiKeyHash ,
11692+ logId : attemptLogId ,
11693+ } ,
11694+ ) ,
11695+ ) ;
11696+ retryAttempt -- ;
11697+ continue ;
11698+ }
11699+
1145911700 if ( willRetryHttpNonStreaming ) {
1146011701 routingAttempts . push (
1146111702 buildRoutingAttempt (
0 commit comments