diff --git a/web/src/components/SessionChat.test.ts b/web/src/components/SessionChat.test.ts index 85f0eb0f..7ee352e9 100644 --- a/web/src/components/SessionChat.test.ts +++ b/web/src/components/SessionChat.test.ts @@ -4,6 +4,7 @@ import { buildGoalStateMessages, isScratchlistHotkeyBlockedTarget, isScratchlistToggleHotkey, + resolvePiContextWindow, shouldAutoClearPendingSchedule, shouldRouteToScratchlist, } from './SessionChat' @@ -46,6 +47,25 @@ describe('applyModelChangeWithReasoningRollback', () => { }) }) +describe('resolvePiContextWindow', () => { + const models = [ + { provider: 'provider-a', modelId: 'shared-model', contextWindow: 100_000 }, + { provider: 'provider-b', modelId: 'shared-model', contextWindow: 200_000 }, + ] + + it('uses the provider-qualified selected model when model ids collide', () => { + expect(resolvePiContextWindow( + models, + { provider: 'provider-b', modelId: 'shared-model' }, + 'shared-model', + )).toBe(200_000) + }) + + it('falls back to the legacy model id when selected-model metadata is absent', () => { + expect(resolvePiContextWindow(models, undefined, 'shared-model')).toBe(100_000) + }) +}) + function userMessage(props: { id: string createdAt: number diff --git a/web/src/components/SessionChat.tsx b/web/src/components/SessionChat.tsx index c8ef4a27..316bac62 100644 --- a/web/src/components/SessionChat.tsx +++ b/web/src/components/SessionChat.tsx @@ -72,6 +72,21 @@ import { isRemoteTerminalSupported } from '@/utils/terminalSupport' type SessionModelSelection = { provider: string; modelId: string } | string | null +export function resolvePiContextWindow( + models: PiModelSummary[] | undefined, + selectedModel: { provider: string; modelId: string } | null | undefined, + legacyModelId: string +): number | undefined { + const model = selectedModel + ? models?.find((candidate) => ( + candidate.provider === selectedModel.provider + && candidate.modelId === selectedModel.modelId + )) + : models?.find((candidate) => candidate.modelId === legacyModelId) + + return model?.contextWindow +} + export async function applyModelChangeWithReasoningRollback(args: { model: SessionModelSelection previousModelReasoningEffort: string | null @@ -648,6 +663,11 @@ function SessionChatInner(props: SessionChatProps) { // Provider-qualified selected model — disambiguates when two providers // share a modelId (hub persists this alongside the legacy modelId string). const piSelectedModel = piMetadata?.piSelectedModel as { provider: string; modelId: string } | null | undefined + const piModels = agentFlavor === 'pi' ? (piModelsState.availableModels.length > 0 ? piModelsState.availableModels : piCachedModels) : undefined + const piContextWindow = useMemo(() => { + if (agentFlavor !== 'pi' || !props.session.model) return undefined + return resolvePiContextWindow(piModels, piSelectedModel, props.session.model) + }, [agentFlavor, piModels, piSelectedModel, props.session.model]) const cursorCatalogReadinessArgs = useMemo(() => ({ sessionLoading: cursorModelsState.isLoading, machineLoading: machineCursorModelsState.isLoading, @@ -1329,7 +1349,7 @@ function SessionChatInner(props: SessionChatProps) { // so Pi model changes go through the dedicated picker only. : undefined } - piModels={agentFlavor === 'pi' ? (piModelsState.availableModels.length > 0 ? piModelsState.availableModels : piCachedModels) : undefined} + piModels={piModels} piSelectedModel={agentFlavor === 'pi' ? piSelectedModel : undefined} availableModelReasoningEffortOptions={ agentFlavor === 'codex' @@ -1350,7 +1370,7 @@ function SessionChatInner(props: SessionChatProps) { backgroundTaskCount={props.session.backgroundTaskCount} contextSize={reduced.latestUsage?.contextSize} contextCacheRead={reduced.latestUsage?.cacheRead} - contextWindow={reduced.latestUsage?.contextWindow} + contextWindow={reduced.latestUsage?.contextWindow ?? piContextWindow} controlledByUser={controlledByUser} onCollaborationModeChange={ codexCollaborationModeSupported && props.session.active && !controlledByUser