mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-01 06:43:36 +00:00
fix: preserve status model alias display
This commit is contained in:
@@ -94,17 +94,7 @@ export function splitTelegramReasoningText(
|
||||
const taggedReasoning = extractThinkingFromTaggedStreamOutsideCode(text);
|
||||
const strippedAnswer = stripReasoningTagsFromText(text, { mode: "strict", trim: "both" });
|
||||
|
||||
if (isReasoning === true) {
|
||||
return { reasoningText: formatReasoningMessage(taggedReasoning || strippedAnswer || text) };
|
||||
}
|
||||
|
||||
if (!taggedReasoning && strippedAnswer === text) {
|
||||
return { answerText: text };
|
||||
}
|
||||
|
||||
const reasoningText = taggedReasoning ? formatReasoningMessage(taggedReasoning) : undefined;
|
||||
const answerText = strippedAnswer || undefined;
|
||||
return { reasoningText, answerText };
|
||||
return { reasoningText: formatReasoningMessage(taggedReasoning || strippedAnswer || text) };
|
||||
}
|
||||
|
||||
type BufferedFinalAnswer = {
|
||||
|
||||
@@ -94,6 +94,24 @@ describe("statusSummaryRuntime configured model normalization", () => {
|
||||
model: "opus-4.6",
|
||||
});
|
||||
|
||||
expect(
|
||||
statusSummaryRuntime.resolveStatusModelComparisonLabel({
|
||||
provider: "anthropic",
|
||||
model: "opus-4.6",
|
||||
defaultProvider: "anthropic",
|
||||
}),
|
||||
).toBe("anthropic/claude-opus-4-6");
|
||||
expect(
|
||||
statusSummaryRuntime.resolveStatusModelLookupRef({
|
||||
provider: "anthropic",
|
||||
model: "opus-4.6",
|
||||
defaultProvider: "anthropic",
|
||||
}),
|
||||
).toEqual({
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-6",
|
||||
});
|
||||
|
||||
expect(normalizeProviderModelIdWithManifestMock).not.toHaveBeenCalled();
|
||||
expect(normalizeProviderModelIdWithRuntimeMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -101,6 +101,55 @@ function resolveConfiguredStatusModelRef(params: {
|
||||
return { provider: params.defaultProvider, model: params.defaultModel };
|
||||
}
|
||||
|
||||
function resolveProviderlessPersistedStatusModelRef(params: {
|
||||
defaultProvider: string;
|
||||
provider?: unknown;
|
||||
model?: unknown;
|
||||
}): { provider: string; model: string } | null {
|
||||
const provider = normalizeOptionalString(params.provider);
|
||||
const model = normalizeOptionalString(params.model);
|
||||
if (
|
||||
!model ||
|
||||
provider ||
|
||||
model.includes("/") ||
|
||||
normalizeLowercaseStringOrEmpty(model) === "openrouter:auto"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
// Status rows report the persisted session text. Shared ref parsing still
|
||||
// canonicalizes provider-local aliases, which would rewrite this display.
|
||||
return { provider: params.defaultProvider, model };
|
||||
}
|
||||
|
||||
function resolveStatusModelLookupRef(params: {
|
||||
provider?: unknown;
|
||||
model?: unknown;
|
||||
defaultProvider?: unknown;
|
||||
}): { provider: string; model: string } | null {
|
||||
const provider = normalizeOptionalString(params.provider);
|
||||
const model = normalizeOptionalString(params.model);
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
const defaultProvider =
|
||||
normalizeOptionalString(params.defaultProvider) ?? provider ?? DEFAULT_PROVIDER;
|
||||
const raw = provider ? `${provider}/${model}` : model;
|
||||
const parsed = parseModelRef(raw, defaultProvider, {
|
||||
allowManifestNormalization: false,
|
||||
allowPluginNormalization: false,
|
||||
});
|
||||
return parsed ?? { provider: provider ?? defaultProvider, model };
|
||||
}
|
||||
|
||||
function resolveStatusModelComparisonLabel(params: {
|
||||
provider?: unknown;
|
||||
model?: unknown;
|
||||
defaultProvider?: unknown;
|
||||
}): string | null {
|
||||
const ref = resolveStatusModelLookupRef(params);
|
||||
return ref ? `${ref.provider}/${ref.model}` : null;
|
||||
}
|
||||
|
||||
function resolveSessionModelRef(
|
||||
cfg: OpenClawConfig,
|
||||
entry?:
|
||||
@@ -114,10 +163,25 @@ function resolveSessionModelRef(
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
agentId,
|
||||
});
|
||||
const defaultProvider = resolved.provider || DEFAULT_PROVIDER;
|
||||
const providerlessPersisted =
|
||||
resolveProviderlessPersistedStatusModelRef({
|
||||
defaultProvider,
|
||||
provider: entry?.providerOverride,
|
||||
model: entry?.modelOverride,
|
||||
}) ??
|
||||
resolveProviderlessPersistedStatusModelRef({
|
||||
defaultProvider,
|
||||
provider: entry?.modelProvider,
|
||||
model: entry?.model,
|
||||
});
|
||||
if (providerlessPersisted) {
|
||||
return providerlessPersisted;
|
||||
}
|
||||
return (
|
||||
// Persisted selected model or overrides describe the active session, not just current config.
|
||||
resolvePersistedSelectedModelRef({
|
||||
defaultProvider: resolved.provider || DEFAULT_PROVIDER,
|
||||
defaultProvider,
|
||||
runtimeProvider: entry?.modelProvider,
|
||||
runtimeModel: entry?.model,
|
||||
overrideProvider: entry?.providerOverride,
|
||||
@@ -171,4 +235,6 @@ export const statusSummaryRuntime = {
|
||||
resolveSessionModelRef,
|
||||
resolveSessionRuntimeLabel,
|
||||
resolveConfiguredStatusModelRef,
|
||||
resolveStatusModelLookupRef,
|
||||
resolveStatusModelComparisonLabel,
|
||||
};
|
||||
|
||||
@@ -80,6 +80,19 @@ vi.mock("./status.summary.runtime.js", () => ({
|
||||
model: "gpt-5.5",
|
||||
})),
|
||||
resolveSessionRuntimeLabel: vi.fn(() => "OpenClaw Default"),
|
||||
resolveStatusModelLookupRef: vi.fn(({ provider, model }) =>
|
||||
typeof model === "string" && model.length > 0
|
||||
? {
|
||||
provider: typeof provider === "string" && provider.length > 0 ? provider : "openai",
|
||||
model,
|
||||
}
|
||||
: null,
|
||||
),
|
||||
resolveStatusModelComparisonLabel: vi.fn(({ provider, model }) =>
|
||||
typeof model === "string" && model.length > 0
|
||||
? `${typeof provider === "string" && provider.length > 0 ? provider : "openai"}/${model}`
|
||||
: null,
|
||||
),
|
||||
resolveContextTokensForModel: vi.fn(() => 200_000),
|
||||
waitForContextWindowCacheLoad: vi.fn(async () => "idle" as const),
|
||||
},
|
||||
@@ -721,4 +734,60 @@ describe("getStatusSummary", () => {
|
||||
expect(summary.sessions.recent[0]?.selectedModel).toBe("openai/gpt-5.5-codex");
|
||||
expect(summary.sessions.recent[0]?.modelSelectionReason).toBeNull();
|
||||
});
|
||||
|
||||
it("does not mark provider-local model aliases as pinned mismatches", async () => {
|
||||
vi.mocked(statusSummaryRuntime.resolveConfiguredStatusModelRef).mockReturnValue({
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-8",
|
||||
});
|
||||
vi.mocked(statusSummaryRuntime.resolveSessionModelRef).mockReturnValue({
|
||||
provider: "anthropic",
|
||||
model: "opus",
|
||||
});
|
||||
vi.mocked(statusSummaryRuntime.resolveStatusModelComparisonLabel).mockImplementation(
|
||||
({ provider, model }) => {
|
||||
if (provider === "anthropic" && model === "opus") {
|
||||
return "anthropic/claude-opus-4-8";
|
||||
}
|
||||
return typeof model === "string" && model.length > 0
|
||||
? `${typeof provider === "string" && provider.length > 0 ? provider : "openai"}/${model}`
|
||||
: null;
|
||||
},
|
||||
);
|
||||
vi.mocked(statusSummaryRuntime.resolveStatusModelLookupRef).mockImplementation(
|
||||
({ provider, model }) => {
|
||||
if (provider === "anthropic" && model === "opus") {
|
||||
return { provider: "anthropic", model: "claude-opus-4-8" };
|
||||
}
|
||||
return typeof model === "string" && model.length > 0
|
||||
? {
|
||||
provider: typeof provider === "string" && provider.length > 0 ? provider : "openai",
|
||||
model,
|
||||
}
|
||||
: null;
|
||||
},
|
||||
);
|
||||
statusSummaryMocks.listSessionEntries.mockReturnValue(
|
||||
toSessionEntrySummaries({
|
||||
"agent:main:main": {
|
||||
sessionId: "session-1",
|
||||
updatedAt: Date.now(),
|
||||
modelOverride: "opus",
|
||||
modelOverrideSource: "user",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const summary = await getStatusSummary();
|
||||
|
||||
expect(summary.sessions.recent[0]?.configuredModel).toBe("anthropic/claude-opus-4-8");
|
||||
expect(summary.sessions.recent[0]?.selectedModel).toBe("anthropic/opus");
|
||||
expect(summary.sessions.recent[0]?.modelSelectionReason).toBeNull();
|
||||
expect(statusSummaryRuntime.resolveSessionRuntimeLabel).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-8",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -254,6 +254,8 @@ export async function getStatusSummary(
|
||||
resolveContextTokensForModel,
|
||||
resolveSessionRuntimeLabel,
|
||||
resolveSessionModelRef,
|
||||
resolveStatusModelComparisonLabel,
|
||||
resolveStatusModelLookupRef,
|
||||
waitForContextWindowCacheLoad,
|
||||
} = await loadStatusSummaryRuntimeModule();
|
||||
const cfg = options.config ?? getRuntimeConfig();
|
||||
@@ -399,29 +401,50 @@ export async function getStatusSummary(
|
||||
const configuredSessionModelLabel = `${configuredForSession.provider ?? DEFAULT_PROVIDER}/${configuredSessionModel}`;
|
||||
const resolvedModel = resolveSessionModelRef(cfg, entry, opts.agentIdOverride);
|
||||
const model = resolvedModel.model ?? configuredSessionModel ?? null;
|
||||
const lookupModel =
|
||||
resolveStatusModelLookupRef({
|
||||
provider: resolvedModel.provider,
|
||||
model,
|
||||
defaultProvider: configuredForSession.provider ?? DEFAULT_PROVIDER,
|
||||
}) ?? resolvedModel;
|
||||
const lookupModelId = lookupModel.model ?? model;
|
||||
const modelContext = await resolveStaticModelContext(
|
||||
resolvedModel.provider,
|
||||
model ?? undefined,
|
||||
lookupModel.provider,
|
||||
lookupModelId ?? undefined,
|
||||
);
|
||||
const selectedModelLabel =
|
||||
resolvedModel.provider && model ? `${resolvedModel.provider}/${model}` : model;
|
||||
const configuredSessionModelComparisonLabel = resolveStatusModelComparisonLabel({
|
||||
provider: configuredForSession.provider ?? DEFAULT_PROVIDER,
|
||||
model: configuredSessionModel,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
});
|
||||
const selectedModelComparisonLabel = resolveStatusModelComparisonLabel({
|
||||
provider: resolvedModel.provider,
|
||||
model,
|
||||
defaultProvider: configuredForSession.provider ?? DEFAULT_PROVIDER,
|
||||
});
|
||||
const modelSelectionDiffers =
|
||||
selectedModelLabel != null &&
|
||||
selectedModelLabel !== configuredSessionModelLabel &&
|
||||
!areRuntimeModelRefsEquivalent(selectedModelLabel, configuredSessionModelLabel) &&
|
||||
selectedModelComparisonLabel != null &&
|
||||
configuredSessionModelComparisonLabel != null &&
|
||||
selectedModelComparisonLabel !== configuredSessionModelComparisonLabel &&
|
||||
!areRuntimeModelRefsEquivalent(
|
||||
selectedModelComparisonLabel,
|
||||
configuredSessionModelComparisonLabel,
|
||||
) &&
|
||||
hasUserPinnedModelSelection(entry);
|
||||
// Session rows show the live selected model but warn only for user-pinned differences.
|
||||
const contextTokens =
|
||||
resolveContextTokensForModel({
|
||||
cfg,
|
||||
sourceCfg: contextSourceConfig,
|
||||
provider: resolvedModel.provider,
|
||||
model,
|
||||
provider: lookupModel.provider,
|
||||
model: lookupModelId,
|
||||
...modelContext,
|
||||
contextTokensOverride: resolveTrustedSessionContextTokens({
|
||||
entry,
|
||||
provider: resolvedModel.provider,
|
||||
model,
|
||||
provider: lookupModel.provider,
|
||||
model: lookupModelId,
|
||||
}),
|
||||
fallbackContextTokens: configContextTokens ?? undefined,
|
||||
allowAsyncLoad: false,
|
||||
@@ -438,8 +461,8 @@ export async function getStatusSummary(
|
||||
const runtime = resolveSessionRuntimeLabel({
|
||||
cfg,
|
||||
entry,
|
||||
provider: resolvedModel.provider,
|
||||
model: model ?? "",
|
||||
provider: lookupModel.provider,
|
||||
model: lookupModelId ?? "",
|
||||
agentId,
|
||||
sessionKey: key,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user