diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 0d5b4612..98cc431e 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Breaking Changes + +- Changed scoped model thinking semantics. Scoped entries without an explicit `:` suffix now inherit the current session thinking level when selected, instead of applying a startup-captured default. + ### Fixed - Fixed IME hardware cursor positioning in the custom extension editor (`ctx.ui.editor()` / extension editor dialog) by propagating focus to the internal `Editor`, preventing the terminal cursor from getting stuck at the bottom-right during composition. diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index 57ae3dbb..ea544e18 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -135,7 +135,7 @@ export interface AgentSessionConfig { settingsManager: SettingsManager; cwd: string; /** Models to cycle through with Ctrl+P (from --models flag) */ - scopedModels?: Array<{ model: Model; thinkingLevel: ThinkingLevel }>; + scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>; /** Resource loader for skills, prompts, themes, context files, system prompt */ resourceLoader: ResourceLoader; /** SDK custom tools registered outside extensions */ @@ -215,7 +215,7 @@ export class AgentSession { readonly sessionManager: SessionManager; readonly settingsManager: SettingsManager; - private _scopedModels: Array<{ model: Model; thinkingLevel: ThinkingLevel }>; + private _scopedModels: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>; // Event subscription state private _unsubscribeAgent?: () => void; @@ -717,12 +717,12 @@ export class AgentSession { } /** Scoped models for cycling (from --models flag) */ - get scopedModels(): ReadonlyArray<{ model: Model; thinkingLevel: ThinkingLevel }> { + get scopedModels(): ReadonlyArray<{ model: Model; thinkingLevel?: ThinkingLevel }> { return this._scopedModels; } /** Update scoped models for cycling */ - setScopedModels(scopedModels: Array<{ model: Model; thinkingLevel: ThinkingLevel }>): void { + setScopedModels(scopedModels: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>): void { this._scopedModels = scopedModels; } @@ -1338,9 +1338,9 @@ export class AgentSession { return this._cycleAvailableModel(direction); } - private async _getScopedModelsWithApiKey(): Promise; thinkingLevel: ThinkingLevel }>> { + private async _getScopedModelsWithApiKey(): Promise; thinkingLevel?: ThinkingLevel }>> { const apiKeysByProvider = new Map(); - const result: Array<{ model: Model; thinkingLevel: ThinkingLevel }> = []; + const result: Array<{ model: Model; thinkingLevel?: ThinkingLevel }> = []; for (const scoped of this._scopedModels) { const provider = scoped.model.provider; @@ -1377,8 +1377,11 @@ export class AgentSession { this.sessionManager.appendModelChange(next.model.provider, next.model.id); this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id); - // Apply thinking level (setThinkingLevel clamps to model capabilities) - this.setThinkingLevel(next.thinkingLevel); + // Apply thinking level. + // - Explicit scoped model thinking level overrides current session level + // - Undefined scoped model thinking level inherits current session level + // setThinkingLevel clamps to model capabilities. + this.setThinkingLevel(next.thinkingLevel ?? this.thinkingLevel); await this._emitModelSelect(next.model, currentModel, "cycle"); diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts index 103a9b91..aa074c30 100644 --- a/packages/coding-agent/src/core/sdk.ts +++ b/packages/coding-agent/src/core/sdk.ts @@ -54,7 +54,7 @@ export interface CreateAgentSessionOptions { /** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */ thinkingLevel?: ThinkingLevel; /** Models available for cycling (Ctrl+P in interactive mode) */ - scopedModels?: Array<{ model: Model; thinkingLevel: ThinkingLevel }>; + scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>; /** Built-in tools to use. Default: codingTools [read, bash, edit, write] */ tools?: Tool[]; diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index a061e778..3b457fab 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -15,7 +15,6 @@ import { listModels } from "./cli/list-models.js"; import { selectSession } from "./cli/session-picker.js"; import { APP_NAME, getAgentDir, getModelsPath, VERSION } from "./config.js"; import { AuthStorage } from "./core/auth-storage.js"; -import { DEFAULT_THINKING_LEVEL } from "./core/defaults.js"; import { exportFromFile } from "./core/export-html/index.js"; import type { LoadExtensionsResult } from "./core/extensions/index.js"; import { KeybindingsManager } from "./core/keybindings.js"; @@ -488,12 +487,13 @@ function buildSessionOptions( options.thinkingLevel = parsed.thinking; } - // Scoped models for Ctrl+P cycling - fill in default thinking level for models without explicit level + // Scoped models for Ctrl+P cycling + // Keep thinking level undefined when not explicitly set in the model pattern. + // Undefined means "inherit current session thinking level" during cycling. if (scopedModels.length > 0) { - const defaultThinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL; options.scopedModels = scopedModels.map((sm) => ({ model: sm.model, - thinkingLevel: sm.thinkingLevel ?? defaultThinkingLevel, + thinkingLevel: sm.thinkingLevel, })); } diff --git a/packages/coding-agent/src/modes/interactive/components/model-selector.ts b/packages/coding-agent/src/modes/interactive/components/model-selector.ts index a58ae510..db6bc6c9 100644 --- a/packages/coding-agent/src/modes/interactive/components/model-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/model-selector.ts @@ -23,7 +23,7 @@ interface ModelItem { interface ScopedModelItem { model: Model; - thinkingLevel: string; + thinkingLevel?: string; } type ModelScope = "all" | "scoped"; diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 43012dc8..b8992628 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -3307,13 +3307,11 @@ export class InteractiveMode { // Helper to update session's scoped models (session-only, no persist) const updateSessionModels = async (enabledIds: Set) => { if (enabledIds.size > 0 && enabledIds.size < allModels.length) { - // Use current session thinking level, not settings default - const currentThinkingLevel = this.session.thinkingLevel; const newScopedModels = await resolveModelScope(Array.from(enabledIds), this.session.modelRegistry); this.session.setScopedModels( newScopedModels.map((sm) => ({ model: sm.model, - thinkingLevel: sm.thinkingLevel ?? currentThinkingLevel, + thinkingLevel: sm.thinkingLevel, })), ); } else {