From 3430299d078f453769c05b359f1908925de61a15 Mon Sep 17 00:00:00 2001 From: ECuteri <155808593+ECuteri@users.noreply.github.com> Date: Sun, 1 Nov 2026 09:50:07 +0000 Subject: [PATCH 1/3] feat: new threads switch to another account when the default is out of usage When the default model's account has hit a 5-hour or weekly limit and no usable fallback model is configured, new threads now pick another account of the same provider with usage left (same model and options), then another provider's default model. Only accounts that report limits are candidates, ordered by remaining headroom. The composer notice names the account used. Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/ChatView.tsx | 13 +++- .../components/chat/ComposerUsageLimits.tsx | 4 +- .../settings/ProjectDefaultsSettings.tsx | 2 +- docs/user/usage.md | 6 ++ packages/shared/src/usageLimits.test.ts | 56 ++++++++++++++ packages/shared/src/usageLimits.ts | 76 ++++++++++++++----- 6 files changed, 130 insertions(+), 27 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 55d951d1bdf80..42bf58a27f45d 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -2577,6 +2577,9 @@ export default function ChatView(props: ChatViewProps) { const fallbackWindow = fallback ? `${fallback.provider.instanceId}:${fallback.resetsAt ?? ""}` : null; + const fallbackProvider = providerStatuses.find( + (provider) => provider.instanceId === fallbackSelection?.instanceId, + ); const fallbackItems: ComposerBannerStackItem[] = fallback && fallbackSelection && @@ -2585,10 +2588,12 @@ export default function ChatView(props: ChatViewProps) { ? [ modelFallbackBannerItem({ fallbackModelName: - providerStatuses - .find((provider) => provider.instanceId === fallbackSelection.instanceId) - ?.models.find((model) => model.slug === fallbackSelection.model)?.name ?? - fallbackSelection.model, + fallbackProvider?.models.find((model) => model.slug === fallbackSelection.model) + ?.name ?? fallbackSelection.model, + fallbackProviderName: + fallbackProvider?.displayName ?? + fallbackProvider?.driver ?? + fallbackSelection.instanceId, defaultProviderName: fallback.provider.displayName ?? fallback.provider.driver, defaultModelName: fallback.provider.models.find((model) => model.slug === defaultSelection.model) diff --git a/apps/web/src/components/chat/ComposerUsageLimits.tsx b/apps/web/src/components/chat/ComposerUsageLimits.tsx index b46614a8b418d..6aab26765600c 100644 --- a/apps/web/src/components/chat/ComposerUsageLimits.tsx +++ b/apps/web/src/components/chat/ComposerUsageLimits.tsx @@ -71,6 +71,7 @@ export function usageLimitsBannerItem( export function modelFallbackBannerItem({ fallbackModelName, + fallbackProviderName, defaultProviderName, defaultModelName, resetsAt, @@ -79,6 +80,7 @@ export function modelFallbackBannerItem({ onDismiss, }: { readonly fallbackModelName: string; + readonly fallbackProviderName: string; readonly defaultProviderName: string; readonly defaultModelName: string; readonly resetsAt: string | null; @@ -90,7 +92,7 @@ export function modelFallbackBannerItem({ id: "fallback-model", variant: "info", icon: , - title: `Using ${fallbackModelName}`, + title: `Using ${fallbackModelName} on ${fallbackProviderName}`, description: `${defaultProviderName} is out of usage${resetsAt ? ` · resets ${formatUpcomingTimestamp(resetsAt, timestampFormat)}` : ""}`, actions: (