@@ -141,6 +141,20 @@ function shouldRethrowAbort(err: unknown): boolean {
141141 return isFallbackAbortError ( err ) && ! isTimeoutError ( err ) ;
142142}
143143
144+ function isTerminalAbort ( signal : AbortSignal | undefined ) : boolean {
145+ if ( ! signal ?. aborted ) {
146+ return false ;
147+ }
148+ const reason = signal . reason ;
149+ if ( ! ( reason instanceof Error ) ) {
150+ return false ;
151+ }
152+ if ( reason . name === "TimeoutError" ) {
153+ return true ;
154+ }
155+ return reason . name === "ClientDisconnectError" ;
156+ }
157+
144158function createModelCandidateCollector ( allowlist : Set < string > | null | undefined ) : {
145159 candidates : ModelCandidate [ ] ;
146160 addExplicitCandidate : ( candidate : ModelCandidate ) => void ;
@@ -245,6 +259,7 @@ async function runFallbackCandidate<T>(params: {
245259 model : string ;
246260 options ?: ModelFallbackRunOptions ;
247261 attribution ?: FailoverAttribution ;
262+ abortSignal ?: AbortSignal ;
248263} ) : Promise < { ok : true ; result : T } | { ok : false ; error : unknown } > {
249264 try {
250265 const result = params . options
@@ -261,6 +276,9 @@ async function runFallbackCandidate<T>(params: {
261276 if ( isNonProviderRuntimeCoordinationError ( err ) ) {
262277 throw err ;
263278 }
279+ if ( isTerminalAbort ( params . abortSignal ) ) {
280+ throw err ;
281+ }
264282 // Normalize abort-wrapped rate-limit errors (e.g. Google Vertex RESOURCE_EXHAUSTED)
265283 // so they become FailoverErrors and continue the fallback loop instead of aborting.
266284 const normalizedFailover = coerceToFailoverError ( err , {
@@ -286,13 +304,15 @@ async function runFallbackAttempt<T>(params: {
286304 attempt : number ;
287305 total : number ;
288306 attribution ?: FailoverAttribution ;
307+ abortSignal ?: AbortSignal ;
289308} ) : Promise < { success : ModelFallbackRunResult < T > } | { error : unknown } > {
290309 const runResult = await runFallbackCandidate ( {
291310 run : params . run ,
292311 provider : params . provider ,
293312 model : params . model ,
294313 options : params . options ,
295314 attribution : params . attribution ,
315+ abortSignal : params . abortSignal ,
296316 } ) ;
297317 if ( runResult . ok ) {
298318 const classification = await params . classifyResult ?.( {
@@ -308,6 +328,9 @@ async function runFallbackAttempt<T>(params: {
308328 attribution : params . attribution ,
309329 } ) ;
310330 if ( classifiedError ) {
331+ if ( isTerminalAbort ( params . abortSignal ) ) {
332+ throw classifiedError ;
333+ }
311334 return { error : classifiedError } ;
312335 }
313336 return {
@@ -1059,6 +1082,7 @@ export async function runWithModelFallback<T>(
10591082 onFallbackStep ?: ModelFallbackStepHandler ;
10601083 classifyResult ?: ModelFallbackResultClassifier < T > ;
10611084 skipAuthProfileRuntime ?: boolean ;
1085+ abortSignal ?: AbortSignal ;
10621086 } & ModelManifestNormalizationContext ,
10631087) : Promise < ModelFallbackRunResult < T > > {
10641088 const candidates = resolveFallbackCandidates ( {
@@ -1301,6 +1325,7 @@ export async function runWithModelFallback<T>(
13011325 attempt : i + 1 ,
13021326 total : candidates . length ,
13031327 attribution : { sessionId : params . sessionId , lane : params . lane } ,
1328+ abortSignal : params . abortSignal ,
13041329 } ) ;
13051330 if ( "success" in attemptRun ) {
13061331 if ( i > 0 || attempts . length > 0 || attemptedDuringCooldown ) {
0 commit comments