Skip to content

Commit 1d4e789

Browse files
committed
fix: preserve status model alias display
1 parent 19cac35 commit 1d4e789

5 files changed

Lines changed: 189 additions & 23 deletions

File tree

extensions/telegram/src/reasoning-lane-coordinator.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,7 @@ export function splitTelegramReasoningText(
9494
const taggedReasoning = extractThinkingFromTaggedStreamOutsideCode(text);
9595
const strippedAnswer = stripReasoningTagsFromText(text, { mode: "strict", trim: "both" });
9696

97-
if (isReasoning === true) {
98-
return { reasoningText: formatReasoningMessage(taggedReasoning || strippedAnswer || text) };
99-
}
100-
101-
if (!taggedReasoning && strippedAnswer === text) {
102-
return { answerText: text };
103-
}
104-
105-
const reasoningText = taggedReasoning ? formatReasoningMessage(taggedReasoning) : undefined;
106-
const answerText = strippedAnswer || undefined;
107-
return { reasoningText, answerText };
97+
return { reasoningText: formatReasoningMessage(taggedReasoning || strippedAnswer || text) };
10898
}
10999

110100
type BufferedFinalAnswer = {

src/commands/status.summary.runtime.normalization.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,24 @@ describe("statusSummaryRuntime configured model normalization", () => {
9494
model: "opus-4.6",
9595
});
9696

97+
expect(
98+
statusSummaryRuntime.resolveStatusModelComparisonLabel({
99+
provider: "anthropic",
100+
model: "opus-4.6",
101+
defaultProvider: "anthropic",
102+
}),
103+
).toBe("anthropic/claude-opus-4-6");
104+
expect(
105+
statusSummaryRuntime.resolveStatusModelLookupRef({
106+
provider: "anthropic",
107+
model: "opus-4.6",
108+
defaultProvider: "anthropic",
109+
}),
110+
).toEqual({
111+
provider: "anthropic",
112+
model: "claude-opus-4-6",
113+
});
114+
97115
expect(normalizeProviderModelIdWithManifestMock).not.toHaveBeenCalled();
98116
expect(normalizeProviderModelIdWithRuntimeMock).not.toHaveBeenCalled();
99117
});

src/commands/status.summary.runtime.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,55 @@ function resolveConfiguredStatusModelRef(params: {
101101
return { provider: params.defaultProvider, model: params.defaultModel };
102102
}
103103

104+
function resolveProviderlessPersistedStatusModelRef(params: {
105+
defaultProvider: string;
106+
provider?: unknown;
107+
model?: unknown;
108+
}): { provider: string; model: string } | null {
109+
const provider = normalizeOptionalString(params.provider);
110+
const model = normalizeOptionalString(params.model);
111+
if (
112+
!model ||
113+
provider ||
114+
model.includes("/") ||
115+
normalizeLowercaseStringOrEmpty(model) === "openrouter:auto"
116+
) {
117+
return null;
118+
}
119+
// Status rows report the persisted session text. Shared ref parsing still
120+
// canonicalizes provider-local aliases, which would rewrite this display.
121+
return { provider: params.defaultProvider, model };
122+
}
123+
124+
function resolveStatusModelLookupRef(params: {
125+
provider?: unknown;
126+
model?: unknown;
127+
defaultProvider?: unknown;
128+
}): { provider: string; model: string } | null {
129+
const provider = normalizeOptionalString(params.provider);
130+
const model = normalizeOptionalString(params.model);
131+
if (!model) {
132+
return null;
133+
}
134+
const defaultProvider =
135+
normalizeOptionalString(params.defaultProvider) ?? provider ?? DEFAULT_PROVIDER;
136+
const raw = provider ? `${provider}/${model}` : model;
137+
const parsed = parseModelRef(raw, defaultProvider, {
138+
allowManifestNormalization: false,
139+
allowPluginNormalization: false,
140+
});
141+
return parsed ?? { provider: provider ?? defaultProvider, model };
142+
}
143+
144+
function resolveStatusModelComparisonLabel(params: {
145+
provider?: unknown;
146+
model?: unknown;
147+
defaultProvider?: unknown;
148+
}): string | null {
149+
const ref = resolveStatusModelLookupRef(params);
150+
return ref ? `${ref.provider}/${ref.model}` : null;
151+
}
152+
104153
function resolveSessionModelRef(
105154
cfg: OpenClawConfig,
106155
entry?:
@@ -114,10 +163,25 @@ function resolveSessionModelRef(
114163
defaultModel: DEFAULT_MODEL,
115164
agentId,
116165
});
166+
const defaultProvider = resolved.provider || DEFAULT_PROVIDER;
167+
const providerlessPersisted =
168+
resolveProviderlessPersistedStatusModelRef({
169+
defaultProvider,
170+
provider: entry?.providerOverride,
171+
model: entry?.modelOverride,
172+
}) ??
173+
resolveProviderlessPersistedStatusModelRef({
174+
defaultProvider,
175+
provider: entry?.modelProvider,
176+
model: entry?.model,
177+
});
178+
if (providerlessPersisted) {
179+
return providerlessPersisted;
180+
}
117181
return (
118182
// Persisted selected model or overrides describe the active session, not just current config.
119183
resolvePersistedSelectedModelRef({
120-
defaultProvider: resolved.provider || DEFAULT_PROVIDER,
184+
defaultProvider,
121185
runtimeProvider: entry?.modelProvider,
122186
runtimeModel: entry?.model,
123187
overrideProvider: entry?.providerOverride,
@@ -171,4 +235,6 @@ export const statusSummaryRuntime = {
171235
resolveSessionModelRef,
172236
resolveSessionRuntimeLabel,
173237
resolveConfiguredStatusModelRef,
238+
resolveStatusModelLookupRef,
239+
resolveStatusModelComparisonLabel,
174240
};

src/commands/status.summary.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,19 @@ vi.mock("./status.summary.runtime.js", () => ({
8080
model: "gpt-5.5",
8181
})),
8282
resolveSessionRuntimeLabel: vi.fn(() => "OpenClaw Default"),
83+
resolveStatusModelLookupRef: vi.fn(({ provider, model }) =>
84+
typeof model === "string" && model.length > 0
85+
? {
86+
provider: typeof provider === "string" && provider.length > 0 ? provider : "openai",
87+
model,
88+
}
89+
: null,
90+
),
91+
resolveStatusModelComparisonLabel: vi.fn(({ provider, model }) =>
92+
typeof model === "string" && model.length > 0
93+
? `${typeof provider === "string" && provider.length > 0 ? provider : "openai"}/${model}`
94+
: null,
95+
),
8396
resolveContextTokensForModel: vi.fn(() => 200_000),
8497
waitForContextWindowCacheLoad: vi.fn(async () => "idle" as const),
8598
},
@@ -721,4 +734,60 @@ describe("getStatusSummary", () => {
721734
expect(summary.sessions.recent[0]?.selectedModel).toBe("openai/gpt-5.5-codex");
722735
expect(summary.sessions.recent[0]?.modelSelectionReason).toBeNull();
723736
});
737+
738+
it("does not mark provider-local model aliases as pinned mismatches", async () => {
739+
vi.mocked(statusSummaryRuntime.resolveConfiguredStatusModelRef).mockReturnValue({
740+
provider: "anthropic",
741+
model: "claude-opus-4-8",
742+
});
743+
vi.mocked(statusSummaryRuntime.resolveSessionModelRef).mockReturnValue({
744+
provider: "anthropic",
745+
model: "opus",
746+
});
747+
vi.mocked(statusSummaryRuntime.resolveStatusModelComparisonLabel).mockImplementation(
748+
({ provider, model }) => {
749+
if (provider === "anthropic" && model === "opus") {
750+
return "anthropic/claude-opus-4-8";
751+
}
752+
return typeof model === "string" && model.length > 0
753+
? `${typeof provider === "string" && provider.length > 0 ? provider : "openai"}/${model}`
754+
: null;
755+
},
756+
);
757+
vi.mocked(statusSummaryRuntime.resolveStatusModelLookupRef).mockImplementation(
758+
({ provider, model }) => {
759+
if (provider === "anthropic" && model === "opus") {
760+
return { provider: "anthropic", model: "claude-opus-4-8" };
761+
}
762+
return typeof model === "string" && model.length > 0
763+
? {
764+
provider: typeof provider === "string" && provider.length > 0 ? provider : "openai",
765+
model,
766+
}
767+
: null;
768+
},
769+
);
770+
statusSummaryMocks.listSessionEntries.mockReturnValue(
771+
toSessionEntrySummaries({
772+
"agent:main:main": {
773+
sessionId: "session-1",
774+
updatedAt: Date.now(),
775+
modelOverride: "opus",
776+
modelOverrideSource: "user",
777+
},
778+
}),
779+
);
780+
781+
const summary = await getStatusSummary();
782+
783+
expect(summary.sessions.recent[0]?.configuredModel).toBe("anthropic/claude-opus-4-8");
784+
expect(summary.sessions.recent[0]?.selectedModel).toBe("anthropic/opus");
785+
expect(summary.sessions.recent[0]?.modelSelectionReason).toBeNull();
786+
expect(statusSummaryRuntime.resolveSessionRuntimeLabel).toHaveBeenCalledWith(
787+
expect.objectContaining({
788+
provider: "anthropic",
789+
model: "claude-opus-4-8",
790+
}),
791+
);
792+
});
724793
});

src/commands/status.summary.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ export async function getStatusSummary(
254254
resolveContextTokensForModel,
255255
resolveSessionRuntimeLabel,
256256
resolveSessionModelRef,
257+
resolveStatusModelComparisonLabel,
258+
resolveStatusModelLookupRef,
257259
waitForContextWindowCacheLoad,
258260
} = await loadStatusSummaryRuntimeModule();
259261
const cfg = options.config ?? getRuntimeConfig();
@@ -399,29 +401,50 @@ export async function getStatusSummary(
399401
const configuredSessionModelLabel = `${configuredForSession.provider ?? DEFAULT_PROVIDER}/${configuredSessionModel}`;
400402
const resolvedModel = resolveSessionModelRef(cfg, entry, opts.agentIdOverride);
401403
const model = resolvedModel.model ?? configuredSessionModel ?? null;
404+
const lookupModel =
405+
resolveStatusModelLookupRef({
406+
provider: resolvedModel.provider,
407+
model,
408+
defaultProvider: configuredForSession.provider ?? DEFAULT_PROVIDER,
409+
}) ?? resolvedModel;
410+
const lookupModelId = lookupModel.model ?? model;
402411
const modelContext = await resolveStaticModelContext(
403-
resolvedModel.provider,
404-
model ?? undefined,
412+
lookupModel.provider,
413+
lookupModelId ?? undefined,
405414
);
406415
const selectedModelLabel =
407416
resolvedModel.provider && model ? `${resolvedModel.provider}/${model}` : model;
417+
const configuredSessionModelComparisonLabel = resolveStatusModelComparisonLabel({
418+
provider: configuredForSession.provider ?? DEFAULT_PROVIDER,
419+
model: configuredSessionModel,
420+
defaultProvider: DEFAULT_PROVIDER,
421+
});
422+
const selectedModelComparisonLabel = resolveStatusModelComparisonLabel({
423+
provider: resolvedModel.provider,
424+
model,
425+
defaultProvider: configuredForSession.provider ?? DEFAULT_PROVIDER,
426+
});
408427
const modelSelectionDiffers =
409-
selectedModelLabel != null &&
410-
selectedModelLabel !== configuredSessionModelLabel &&
411-
!areRuntimeModelRefsEquivalent(selectedModelLabel, configuredSessionModelLabel) &&
428+
selectedModelComparisonLabel != null &&
429+
configuredSessionModelComparisonLabel != null &&
430+
selectedModelComparisonLabel !== configuredSessionModelComparisonLabel &&
431+
!areRuntimeModelRefsEquivalent(
432+
selectedModelComparisonLabel,
433+
configuredSessionModelComparisonLabel,
434+
) &&
412435
hasUserPinnedModelSelection(entry);
413436
// Session rows show the live selected model but warn only for user-pinned differences.
414437
const contextTokens =
415438
resolveContextTokensForModel({
416439
cfg,
417440
sourceCfg: contextSourceConfig,
418-
provider: resolvedModel.provider,
419-
model,
441+
provider: lookupModel.provider,
442+
model: lookupModelId,
420443
...modelContext,
421444
contextTokensOverride: resolveTrustedSessionContextTokens({
422445
entry,
423-
provider: resolvedModel.provider,
424-
model,
446+
provider: lookupModel.provider,
447+
model: lookupModelId,
425448
}),
426449
fallbackContextTokens: configContextTokens ?? undefined,
427450
allowAsyncLoad: false,
@@ -438,8 +461,8 @@ export async function getStatusSummary(
438461
const runtime = resolveSessionRuntimeLabel({
439462
cfg,
440463
entry,
441-
provider: resolvedModel.provider,
442-
model: model ?? "",
464+
provider: lookupModel.provider,
465+
model: lookupModelId ?? "",
443466
agentId,
444467
sessionKey: key,
445468
});

0 commit comments

Comments
 (0)