diff --git a/packages/shared/src/usageLimits.test.ts b/packages/shared/src/usageLimits.test.ts index 23447ae7930b0..41cd5f097f117 100644 --- a/packages/shared/src/usageLimits.test.ts +++ b/packages/shared/src/usageLimits.test.ts @@ -132,6 +132,9 @@ describe("new thread model fallback", () => { ["antigravity", ["gemini-weekly"], "antigravity-default", true], ["opencode", ["go_weekly"], "opencode/big-pickle", false], ["opencode", ["go_weekly"], "opencode-go/big-pickle", true], + ["claudeAgent", ["seven_day_fable"], "claude-fable-5-1", true], + ["claudeAgent", ["seven_day_fable"], "claude-opus-5-5", false], + ["claudeAgent", ["seven_day"], "claude-opus-5-5", true], ] as const)("matches %s pools %s to %s", (driver, ids, model, usesFallback) => { const defaultSelection = { ...selection, model }; expect( @@ -151,6 +154,25 @@ describe("new thread model fallback", () => { }).selection, ).toEqual(usesFallback ? fallback : defaultSelection); }); + it("falls back to another model on the same Claude instance when only its model limit is spent", () => { + const claude = provider({ + instanceId: fallback.instanceId, + driver: ProviderDriverKind.make("claudeAgent"), + usageLimits: { + checkedAt: limited.checkedAt, + windows: [{ ...window, id: "seven_day_fable", usedPercent: 100 }], + }, + }); + const opus = { ...fallback, model: "claude-opus-5-5" }; + expect( + resolveNewThreadModelSelection({ + ...defaults, + defaultSelection: fallback, + fallbackSelection: opus, + providers: [claude], + }).selection, + ).toEqual(opus); + }); }); describe("pace", () => { diff --git a/packages/shared/src/usageLimits.ts b/packages/shared/src/usageLimits.ts index bf7dfea4e04bf..0b6bd8fae89fd 100644 --- a/packages/shared/src/usageLimits.ts +++ b/packages/shared/src/usageLimits.ts @@ -56,6 +56,13 @@ export function exhaustedUsageWindow( return exhausted.find( (window) => !window.id.startsWith("go_") || model.startsWith("opencode-go/"), ); + case "claudeAgent": { + const slug = model.toLowerCase().replace(/[^a-z0-9]+/g, "_"); + return exhausted.find((window) => { + const scope = window.id.match(/^seven_day_(.+)$/)?.[1]; + return scope === undefined || slug.includes(scope); + }); + } default: return exhausted[0]; }