From bb10aacb23f12ef34f9cb4adb0715996e1aea788 Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Fri, 13 Mar 2026 10:50:19 -0600 Subject: [PATCH] feat: add /thinking slash command for toggling thinking level (#129) Co-Authored-By: Claude Opus 4.6 --- .../src/core/slash-commands.ts | 1 + .../components/settings-selector.ts | 4 +- .../src/modes/interactive/interactive-mode.ts | 60 ++++++++++++++++++- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/packages/pi-coding-agent/src/core/slash-commands.ts b/packages/pi-coding-agent/src/core/slash-commands.ts index 0f814a6ba..fd4b667b5 100644 --- a/packages/pi-coding-agent/src/core/slash-commands.ts +++ b/packages/pi-coding-agent/src/core/slash-commands.ts @@ -34,5 +34,6 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray = [ { name: "compact", description: "Manually compact the session context" }, { name: "resume", description: "Resume a different session" }, { name: "reload", description: "Reload extensions, skills, prompts, and themes" }, + { name: "thinking", description: "Set thinking level (off/minimal/low/medium/high/xhigh)" }, { name: "quit", description: "Quit pi" }, ]; diff --git a/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts b/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts index ae0b36748..dea8f940f 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts @@ -13,7 +13,7 @@ import { import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; -const THINKING_DESCRIPTIONS: Record = { +export const THINKING_DESCRIPTIONS: Record = { off: "No reasoning", minimal: "Very brief reasoning (~1k tokens)", low: "Light reasoning (~2k tokens)", @@ -73,7 +73,7 @@ export interface SettingsCallbacks { /** * A submenu component for selecting from a list of options. */ -class SelectSubmenu extends Container { +export class SelectSubmenu extends Container { private selectList: SelectList; constructor( diff --git a/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts b/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts index 409822947..b1fb49001 100644 --- a/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts @@ -7,7 +7,7 @@ import * as crypto from "node:crypto"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import type { AgentMessage } from "@gsd/pi-agent-core"; +import type { AgentMessage, ThinkingLevel } from "@gsd/pi-agent-core"; import type { AssistantMessage, ImageContent, Message, Model, OAuthProviderId } from "@gsd/pi-ai"; import type { AutocompleteItem, @@ -85,7 +85,7 @@ import { ModelSelectorComponent } from "./components/model-selector.js"; import { OAuthSelectorComponent } from "./components/oauth-selector.js"; import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js"; import { SessionSelectorComponent } from "./components/session-selector.js"; -import { SettingsSelectorComponent } from "./components/settings-selector.js"; +import { SelectSubmenu, SettingsSelectorComponent, THINKING_DESCRIPTIONS } from "./components/settings-selector.js"; import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js"; import { ToolExecutionComponent } from "./components/tool-execution.js"; import { TreeSelectorComponent } from "./components/tree-selector.js"; @@ -2015,6 +2015,12 @@ export class InteractiveMode { await this.handleReloadCommand(); return; } + if (text === "/thinking" || text.startsWith("/thinking ")) { + const arg = text.startsWith("/thinking ") ? text.slice(10).trim() : undefined; + this.editor.setText(""); + this.handleThinkingCommand(arg); + return; + } if (text === "/debug") { this.handleDebugCommand(); this.editor.setText(""); @@ -2745,6 +2751,56 @@ export class InteractiveMode { } } + private handleThinkingCommand(arg?: string): void { + const availableLevels = this.session.getAvailableThinkingLevels(); + if (availableLevels.length === 0) { + this.showStatus("Current model does not support thinking"); + return; + } + + if (arg) { + const level = arg.toLowerCase() as ThinkingLevel; + if (!availableLevels.includes(level)) { + this.showStatus(`Invalid thinking level "${arg}". Available: ${availableLevels.join(", ")}`); + return; + } + this.session.setThinkingLevel(level); + this.footer.invalidate(); + this.updateEditorBorderColor(); + this.showStatus(`Thinking level: ${level}`); + return; + } + + this.showThinkingSelector(); + } + + private showThinkingSelector(): void { + const availableLevels = this.session.getAvailableThinkingLevels(); + this.showSelector((done) => { + const selector = new SelectSubmenu( + "Thinking Level", + "Select reasoning depth for thinking-capable models", + availableLevels.map((level) => ({ + value: level, + label: level, + description: THINKING_DESCRIPTIONS[level], + })), + this.session.thinkingLevel, + (value) => { + this.session.setThinkingLevel(value as ThinkingLevel); + this.footer.invalidate(); + this.updateEditorBorderColor(); + done(); + this.showStatus(`Thinking level: ${value}`); + }, + () => { + done(); + }, + ); + return { component: selector, focus: selector }; + }); + } + private async cycleModel(direction: "forward" | "backward"): Promise { try { const result = await this.session.cycleModel(direction);