From eb7d89f4b9d5e7de330073ac835006a7b91148ee Mon Sep 17 00:00:00 2001 From: Hemant Sudarshan <141361950+HemantSudarshan@users.noreply.github.com> Date: Thu, 30 Apr 2026 00:28:21 +0530 Subject: [PATCH] fix(status): honor channel model context windows --- src/auto-reply/status.test.ts | 45 +++++++++++++ src/status/status-message.ts | 118 +++++++++++++++++++++------------- 2 files changed, 118 insertions(+), 45 deletions(-) diff --git a/src/auto-reply/status.test.ts b/src/auto-reply/status.test.ts index 528bf303e48..e24edd95f3c 100644 --- a/src/auto-reply/status.test.ts +++ b/src/auto-reply/status.test.ts @@ -601,6 +601,51 @@ describe("buildStatusMessage", () => { expect(normalized).toContain("channel override"); }); + it("uses the channel override model context window instead of stale persisted context", () => { + const text = buildStatusMessage({ + config: { + channels: { + modelByChannel: { + discord: { + "123": "minimax-portal/MiniMax-M2.7", + }, + }, + }, + models: { + providers: { + "minimax-portal": { + models: [{ id: "MiniMax-M2.7", contextWindow: 200_000 }], + }, + anthropic: { + models: [{ id: "claude-opus-4-6", contextWindow: 1_048_576 }], + }, + }, + }, + } as unknown as OpenClawConfig, + agent: { + model: "minimax-portal/MiniMax-M2.7", + contextTokens: 1_048_576, + }, + sessionEntry: { + sessionId: "channel-context-window", + updatedAt: 0, + channel: "discord", + groupId: "123", + totalTokens: 49_000, + contextTokens: 1_048_576, + }, + sessionKey: "agent:main:main", + sessionScope: "per-sender", + queue: { mode: "collect", depth: 0 }, + }); + const normalized = normalizeTestText(text); + + expect(normalized).toContain("Model: minimax-portal/MiniMax-M2.7"); + expect(normalized).toContain("channel override"); + expect(normalized).toContain("Context: 49k/200k"); + expect(normalized).not.toContain("Context: 49k/1.0m"); + }); + it("shows 1M context window when anthropic context1m is enabled", () => { const text = buildStatusMessage({ config: { diff --git a/src/status/status-message.ts b/src/status/status-message.ts index 064acb44d2f..b9781bd9d31 100644 --- a/src/status/status-message.ts +++ b/src/status/status-message.ts @@ -489,6 +489,57 @@ const formatVoiceModeLine = ( return parts.join(" · "); }; +function resolveChannelModelNote(params: { + config?: OpenClawConfig; + entry?: SessionEntry; + selectedProvider: string; + selectedModel: string; + parentSessionKey?: string; +}): string | undefined { + if (!params.config || !params.entry) { + return undefined; + } + if ( + normalizeOptionalString(params.entry.modelOverride) || + normalizeOptionalString(params.entry.providerOverride) + ) { + return undefined; + } + const channelOverride = resolveChannelModelOverride({ + cfg: params.config, + channel: params.entry.channel ?? params.entry.origin?.provider, + groupId: params.entry.groupId, + groupChatType: params.entry.chatType ?? params.entry.origin?.chatType, + groupChannel: params.entry.groupChannel, + groupSubject: params.entry.subject, + parentSessionKey: params.parentSessionKey, + }); + if (!channelOverride) { + return undefined; + } + const aliasIndex = buildModelAliasIndex({ + cfg: params.config, + defaultProvider: DEFAULT_PROVIDER, + allowPluginNormalization: false, + }); + const resolvedOverride = resolveModelRefFromString({ + raw: channelOverride.model, + defaultProvider: DEFAULT_PROVIDER, + aliasIndex, + allowPluginNormalization: false, + }); + if (!resolvedOverride) { + return undefined; + } + if ( + resolvedOverride.ref.provider !== params.selectedProvider || + resolvedOverride.ref.model !== params.selectedModel + ) { + return undefined; + } + return "channel override"; +} + export function buildStatusMessage(args: StatusArgs): string { const now = args.now ?? Date.now(); const entry = args.sessionEntry; @@ -650,10 +701,21 @@ export function buildStatusMessage(args: StatusArgs): string { model: contextLookupModel, allowAsyncLoad: false, }); + const channelModelNote = resolveChannelModelNote({ + config: args.config, + entry, + selectedProvider, + selectedModel, + parentSessionKey: args.parentSessionKey, + }); const persistedContextTokens = typeof entry?.contextTokens === "number" && entry.contextTokens > 0 ? entry.contextTokens : undefined; + const agentContextTokens = + typeof args.agent?.contextTokens === "number" && args.agent.contextTokens > 0 + ? args.agent.contextTokens + : undefined; const explicitRuntimeContextTokens = typeof args.runtimeContextTokens === "number" && args.runtimeContextTokens > 0 ? args.runtimeContextTokens @@ -669,6 +731,15 @@ export function buildStatusMessage(args: StatusArgs): string { ? Math.min(explicitConfiguredContextTokens, activeContextTokens) : explicitConfiguredContextTokens : undefined; + const channelOverrideContextTokens = channelModelNote + ? (explicitRuntimeContextTokens ?? + cappedConfiguredContextTokens ?? + (typeof activeContextTokens === "number" + ? typeof agentContextTokens === "number" + ? Math.min(agentContextTokens, activeContextTokens) + : activeContextTokens + : agentContextTokens)) + : undefined; // When a fallback model is active, the selected-model context limit that // callers keep on the agent config is often stale. Prefer an explicit runtime // snapshot when available. Separately, callers can pass an explicit configured @@ -715,7 +786,8 @@ export function buildStatusMessage(args: StatusArgs): string { cfg: contextConfig, ...(contextLookupProvider ? { provider: contextLookupProvider } : {}), model: contextLookupModel, - contextTokensOverride: persistedContextTokens ?? args.agent?.contextTokens, + contextTokensOverride: + channelOverrideContextTokens ?? persistedContextTokens ?? agentContextTokens, fallbackContextTokens: DEFAULT_CONTEXT_TOKENS, allowAsyncLoad: false, }) ?? DEFAULT_CONTEXT_TOKENS); @@ -854,50 +926,6 @@ export function buildStatusMessage(args: StatusArgs): string { const costLabel = showCost && hasUsage ? formatUsd(cost) : undefined; const selectedAuthLabel = selectedAuthLabelValue ? ` · 🔑 ${selectedAuthLabelValue}` : ""; - const channelModelNote = (() => { - if (!args.config || !entry) { - return undefined; - } - if ( - normalizeOptionalString(entry.modelOverride) || - normalizeOptionalString(entry.providerOverride) - ) { - return undefined; - } - const channelOverride = resolveChannelModelOverride({ - cfg: args.config, - channel: entry.channel ?? entry.origin?.provider, - groupId: entry.groupId, - groupChatType: entry.chatType ?? entry.origin?.chatType, - groupChannel: entry.groupChannel, - groupSubject: entry.subject, - parentSessionKey: args.parentSessionKey, - }); - if (!channelOverride) { - return undefined; - } - const aliasIndex = buildModelAliasIndex({ - cfg: args.config, - defaultProvider: DEFAULT_PROVIDER, - allowPluginNormalization: false, - }); - const resolvedOverride = resolveModelRefFromString({ - raw: channelOverride.model, - defaultProvider: DEFAULT_PROVIDER, - aliasIndex, - allowPluginNormalization: false, - }); - if (!resolvedOverride) { - return undefined; - } - if ( - resolvedOverride.ref.provider !== selectedProvider || - resolvedOverride.ref.model !== selectedModel - ) { - return undefined; - } - return "channel override"; - })(); const modelNote = channelModelNote ? ` · ${channelModelNote}` : ""; const modelLine = `🧠 Model: ${selectedModelLabel}${selectedAuthLabel}${modelNote}`;