feat(auto-reply): show thinking level in model summary

- Add thinking level display to /model summary output
- Resolve effective thinking level from provider catalog and runtime
- Move thinkingCatalog construction before maybeHandleModelDirectiveInfo call
- Align thinkingCatalog type to ThinkingCatalogEntry[]
- Make runtimePolicySessionKey optional
- Add tests for thinking level in model summary and channel-specific summaries

Rebased from PR #111709 (commits addba5549ba, a9f9f46406e, 72e75e994c6) onto origin/main.
This commit is contained in:
Kesava
2026-07-25 13:45:16 +05:30
committed by Ayaan Zaidi
parent b937638d8b
commit 8a8ea037a4
3 changed files with 91 additions and 7 deletions
@@ -119,7 +119,12 @@ export async function handleDirectiveOnly(
commandAuthorized: params.commandAuthorized,
senderIsOwner: params.senderIsOwner,
});
const thinkingCatalog =
params.thinkingCatalog && params.thinkingCatalog.length > 0
? params.thinkingCatalog
: allowedModelCatalog.length > 0
? allowedModelCatalog
: undefined;
const modelInfo = await maybeHandleModelDirectiveInfo({
directives,
cfg: params.cfg,
@@ -133,6 +138,9 @@ export async function handleDirectiveOnly(
policyAliasIndex,
allowedModelKeys,
allowedModelCatalog,
currentThinkLevel: currentThinkLevel ?? "off",
thinkingCatalog,
runtimePolicySessionKey,
resetModelOverride,
workspaceDir: params.workspaceDir,
surface: params.surface,
@@ -186,12 +194,6 @@ export async function handleDirectiveOnly(
sessionKey: runtimePolicySessionKey,
sessionEntry: prospectiveSessionEntry,
});
const thinkingCatalog =
params.thinkingCatalog && params.thinkingCatalog.length > 0
? params.thinkingCatalog
: allowedModelCatalog.length > 0
? allowedModelCatalog
: undefined;
const fastModeState = resolveFastModeState({
cfg: params.cfg,
provider: resolvedProvider,
@@ -638,6 +638,8 @@ async function resolveModelInfoReply(
aliasIndex: baseAliasIndex(),
allowedModelKeys: new Set(),
allowedModelCatalog: [],
currentThinkLevel: "medium",
runtimePolicySessionKey: "agent:main:main",
resetModelOverride: false,
...overrides,
});
@@ -648,10 +650,65 @@ describe("/model chat UX", () => {
const reply = await resolveModelInfoReply();
expect(reply?.text).toContain("Current:");
expect(reply?.text).toContain("Think: medium (change with /think <level>)");
expect(reply?.text).toContain("Browse: /models");
expect(reply?.text).toContain("Switch: /model <provider/model>");
});
it("includes the thinking level in channel-specific model summaries", async () => {
const registry = createEmptyPluginRegistry();
registry.channels = [
{
pluginId: "test",
plugin: {
id: "telegram",
commands: {
buildModelBrowseChannelData: () => ({ telegram: { inlineKeyboard: [] } }),
},
},
source: "test",
},
] as never;
setActivePluginRegistry(registry);
const reply = await resolveModelInfoReply({ surface: "telegram" });
expect(reply?.channelData).toBeDefined();
expect(reply?.text).toContain("Think: medium (change with /think <level>)");
});
it("shows the effective thinking level for the selected runtime", async () => {
setDirectiveTestProviders([
{
id: "openai",
label: "OpenAI",
auth: [],
resolveThinkingProfile: ({ agentRuntime }) => ({
levels: [
{ id: "off" },
{ id: "low" },
{ id: "medium" },
{ id: "high" },
{ id: "max" },
...(agentRuntime === "openclaw" ? ([{ id: "ultra" }] as const) : []),
],
}),
},
]);
const reply = await resolveModelInfoReply({
provider: "openai",
model: "gpt-5.6-luna",
defaultProvider: "openai",
defaultModel: "gpt-5.6-luna",
currentThinkLevel: "ultra",
sessionEntry: { agentRuntimeOverride: "codex" },
});
expect(reply?.text).toContain("Think: max (change with /think <level>)");
expect(reply?.text).not.toContain("Think: ultra");
});
it("treats /model list as a models browser alias, not a model id", async () => {
const reply = await resolveModelInfoReply({
directives: parseInlineDirectives("/model list"),
@@ -21,11 +21,14 @@ import {
import { RUNTIME_MODEL_VISIBILITY_NORMALIZATION } from "../../agents/model-visibility-policy.js";
import { buildAgentRuntimeAuthPlan } from "../../agents/runtime-plan/auth.js";
import { resolveSessionRuntimeOverrideForProvider } from "../../agents/session-runtime-compat.js";
import { resolveEffectiveAgentRuntime } from "../../agents/thinking-runtime.js";
import { getChannelPlugin } from "../../channels/plugins/index.js";
import type { SessionEntry } from "../../config/sessions.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { shortenHomePath } from "../../utils.js";
import { resolveSelectedAndActiveModel } from "../model-runtime.js";
import { resolveSupportedThinkingLevel } from "../thinking.js";
import type { ThinkingCatalogEntry } from "../thinking.shared.js";
import type { ReplyPayload } from "../types.js";
import { resolveModelsCommandReply } from "./commands-models.js";
import {
@@ -38,6 +41,7 @@ import {
resolveProviderEndpointLabel,
} from "./directive-handling.model-picker.js";
import type { InlineDirectives } from "./directive-handling.parse.js";
import type { ThinkLevel } from "./directives.js";
function isMissingAuthLabel(auth: { label: string; source: string }): boolean {
return auth.label === "missing" && auth.source === "missing";
@@ -380,6 +384,9 @@ export async function maybeHandleModelDirectiveInfo(params: {
policyAliasIndex?: ModelAliasIndex;
allowedModelKeys: ReadonlySet<string>;
allowedModelCatalog: Array<{ provider: string; id?: string; name?: string }>;
currentThinkLevel: ThinkLevel;
thinkingCatalog?: ThinkingCatalogEntry[];
runtimePolicySessionKey?: string;
resetModelOverride: boolean;
workspaceDir?: string;
surface?: string;
@@ -435,6 +442,22 @@ export async function maybeHandleModelDirectiveInfo(params: {
sessionEntry: params.sessionEntry,
});
const current = modelRefs.selected.label;
const thinkingRuntime = resolveEffectiveAgentRuntime({
cfg: params.cfg,
provider: params.provider,
modelId: params.model,
agentId: params.activeAgentId,
sessionKey: params.runtimePolicySessionKey,
sessionEntry: params.sessionEntry,
});
const effectiveThinkLevel = resolveSupportedThinkingLevel({
provider: params.provider,
model: params.model,
level: params.currentThinkLevel,
catalog: params.thinkingCatalog,
agentRuntime: thinkingRuntime,
});
const thinkingLine = `Think: ${effectiveThinkLevel} (change with /think <level>)`;
const activeRuntimeLine = modelRefs.activeDiffers
? `Active: ${modelRefs.active.label} (runtime)`
: null;
@@ -445,6 +468,7 @@ export async function maybeHandleModelDirectiveInfo(params: {
text: [
`Current: ${current}${modelRefs.activeDiffers ? " (selected)" : ""}`,
activeRuntimeLine,
thinkingLine,
"",
"Tap below to browse models, or use:",
"/model <provider/model> to switch",
@@ -461,6 +485,7 @@ export async function maybeHandleModelDirectiveInfo(params: {
text: [
`Current: ${current}${modelRefs.activeDiffers ? " (selected)" : ""}`,
activeRuntimeLine,
thinkingLine,
"",
"Switch: /model <provider/model>",
"Runtime: /model <provider/model> --runtime <runtime>",