@@ -8,7 +8,11 @@ import { AUTH_STORE_VERSION } from "./auth-profiles/constants.js";
88import type { AuthProfileStore } from "./auth-profiles/types.js" ;
99import { FailoverError } from "./failover-error.js" ;
1010import { LiveSessionModelSwitchError } from "./live-model-switch-error.js" ;
11- import { runWithImageModelFallback , runWithModelFallback } from "./model-fallback.js" ;
11+ import {
12+ FallbackSummaryError ,
13+ runWithImageModelFallback ,
14+ runWithModelFallback ,
15+ } from "./model-fallback.js" ;
1216import { classifyEmbeddedPiRunResultForModelFallback } from "./pi-embedded-runner/result-fallback-classifier.js" ;
1317import type { EmbeddedPiRunResult } from "./pi-embedded-runner/types.js" ;
1418import { makeModelFallbackCfg } from "./test-helpers/model-fallback-config-fixture.js" ;
@@ -474,6 +478,51 @@ describe("runWithModelFallback", () => {
474478 } ) ;
475479 } ) ;
476480
481+ it ( "carries request attribution through exhausted fallback summaries" , async ( ) => {
482+ const cfg = makeCfg ( {
483+ agents : {
484+ defaults : {
485+ model : {
486+ primary : "openai/gpt-5.4" ,
487+ fallbacks : [ "anthropic/claude-opus-4-6" ] ,
488+ } ,
489+ } ,
490+ } ,
491+ } ) ;
492+ const run = vi
493+ . fn ( )
494+ . mockRejectedValueOnce ( Object . assign ( new Error ( "rate limit exceeded" ) , { status : 429 } ) )
495+ . mockRejectedValueOnce ( Object . assign ( new Error ( "overloaded" ) , { status : 503 } ) ) ;
496+
497+ try {
498+ await runWithModelFallback ( {
499+ cfg,
500+ provider : "openai" ,
501+ model : "gpt-5.4" ,
502+ runId : "run-42713" ,
503+ sessionId : "session:browser-42713" ,
504+ lane : "answer" ,
505+ run,
506+ } ) ;
507+ throw new Error ( "expected fallback summary" ) ;
508+ } catch ( err ) {
509+ expect ( err ) . toBeInstanceOf ( FallbackSummaryError ) ;
510+ if ( ! ( err instanceof FallbackSummaryError ) ) {
511+ throw err ;
512+ }
513+ expect ( err ) . toMatchObject ( {
514+ name : "FallbackSummaryError" ,
515+ sessionId : "session:browser-42713" ,
516+ lane : "answer" ,
517+ } ) ;
518+ expect ( err . cause ) . toMatchObject ( {
519+ name : "FailoverError" ,
520+ sessionId : "session:browser-42713" ,
521+ lane : "answer" ,
522+ } ) ;
523+ }
524+ } ) ;
525+
477526 it ( "uses optional result classification to continue to configured fallbacks" , async ( ) => {
478527 const cfg = makeCfg ( {
479528 agents : {
0 commit comments