fix(status): honor channel model context windows

This commit is contained in:
Hemant Sudarshan
2026-04-30 00:28:21 +05:30
committed by GitHub
parent 57e4994caf
commit eb7d89f4b9
2 changed files with 118 additions and 45 deletions

View File

@@ -601,6 +601,51 @@ describe("buildStatusMessage", () => {
expect(normalized).toContain("channel override"); 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", () => { it("shows 1M context window when anthropic context1m is enabled", () => {
const text = buildStatusMessage({ const text = buildStatusMessage({
config: { config: {

View File

@@ -489,6 +489,57 @@ const formatVoiceModeLine = (
return parts.join(" · "); 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 { export function buildStatusMessage(args: StatusArgs): string {
const now = args.now ?? Date.now(); const now = args.now ?? Date.now();
const entry = args.sessionEntry; const entry = args.sessionEntry;
@@ -650,10 +701,21 @@ export function buildStatusMessage(args: StatusArgs): string {
model: contextLookupModel, model: contextLookupModel,
allowAsyncLoad: false, allowAsyncLoad: false,
}); });
const channelModelNote = resolveChannelModelNote({
config: args.config,
entry,
selectedProvider,
selectedModel,
parentSessionKey: args.parentSessionKey,
});
const persistedContextTokens = const persistedContextTokens =
typeof entry?.contextTokens === "number" && entry.contextTokens > 0 typeof entry?.contextTokens === "number" && entry.contextTokens > 0
? entry.contextTokens ? entry.contextTokens
: undefined; : undefined;
const agentContextTokens =
typeof args.agent?.contextTokens === "number" && args.agent.contextTokens > 0
? args.agent.contextTokens
: undefined;
const explicitRuntimeContextTokens = const explicitRuntimeContextTokens =
typeof args.runtimeContextTokens === "number" && args.runtimeContextTokens > 0 typeof args.runtimeContextTokens === "number" && args.runtimeContextTokens > 0
? args.runtimeContextTokens ? args.runtimeContextTokens
@@ -669,6 +731,15 @@ export function buildStatusMessage(args: StatusArgs): string {
? Math.min(explicitConfiguredContextTokens, activeContextTokens) ? Math.min(explicitConfiguredContextTokens, activeContextTokens)
: explicitConfiguredContextTokens : explicitConfiguredContextTokens
: undefined; : 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 // 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 // callers keep on the agent config is often stale. Prefer an explicit runtime
// snapshot when available. Separately, callers can pass an explicit configured // snapshot when available. Separately, callers can pass an explicit configured
@@ -715,7 +786,8 @@ export function buildStatusMessage(args: StatusArgs): string {
cfg: contextConfig, cfg: contextConfig,
...(contextLookupProvider ? { provider: contextLookupProvider } : {}), ...(contextLookupProvider ? { provider: contextLookupProvider } : {}),
model: contextLookupModel, model: contextLookupModel,
contextTokensOverride: persistedContextTokens ?? args.agent?.contextTokens, contextTokensOverride:
channelOverrideContextTokens ?? persistedContextTokens ?? agentContextTokens,
fallbackContextTokens: DEFAULT_CONTEXT_TOKENS, fallbackContextTokens: DEFAULT_CONTEXT_TOKENS,
allowAsyncLoad: false, allowAsyncLoad: false,
}) ?? DEFAULT_CONTEXT_TOKENS); }) ?? DEFAULT_CONTEXT_TOKENS);
@@ -854,50 +926,6 @@ export function buildStatusMessage(args: StatusArgs): string {
const costLabel = showCost && hasUsage ? formatUsd(cost) : undefined; const costLabel = showCost && hasUsage ? formatUsd(cost) : undefined;
const selectedAuthLabel = selectedAuthLabelValue ? ` · 🔑 ${selectedAuthLabelValue}` : ""; 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 modelNote = channelModelNote ? ` · ${channelModelNote}` : "";
const modelLine = `🧠 Model: ${selectedModelLabel}${selectedAuthLabel}${modelNote}`; const modelLine = `🧠 Model: ${selectedModelLabel}${selectedAuthLabel}${modelNote}`;