Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Improved search reliability for Perplexity provider by forcing retrieval for all queries
- Fixed JS eval cells losing top-level `function` and `var` declarations across cells when the defining cell contained top-level `await` — the async wrapper scoped them to the cell's IIFE instead of publishing them to the worker global
- Fixed temporary model picks (`Alt+P`, `/switch`, `/model --temporary`) ignoring explicit thinking suffixes from matching configured model roles. ([#5290](https://github.com/can1357/oh-my-pi/issues/5290))

## [16.4.7] - 2026-07-12

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -688,8 +688,8 @@ export class SelectorController {
},
onPick: async (model, selector) => {
try {
// Session-only: update agent state but don't persist the model to settings.
await this.ctx.session.setModelTemporary(model);
const roleThinkingLevel = this.ctx.session.resolveTemporaryModelThinkingLevel(model);
await this.ctx.session.setModelTemporary(model, roleThinkingLevel);
this.ctx.statusLine.invalidate();
this.ctx.updateEditorBorderColor();
const roleSelectorHint = this.ctx.keybindings.getKeys("app.model.select")[0] ?? "Alt+M";
Expand Down
26 changes: 25 additions & 1 deletion packages/coding-agent/src/session/agent-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ import {
resolveModelOverride,
resolveModelRoleValue,
} from "../config/model-resolver";
import { MODEL_ROLE_IDS, MODEL_ROLES } from "../config/model-roles";
import { getKnownRoleIds, MODEL_ROLE_IDS, MODEL_ROLES } from "../config/model-roles";
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
import { buildServiceTierByFamily, serviceTierForAllFamilies, serviceTierSettingToTier } from "../config/service-tier";
import type { Settings, SkillsSettings } from "../config/settings";
Expand Down Expand Up @@ -7321,6 +7321,30 @@ export class AgentSession {
return this.#resolveRoleModelFull(role, this.#modelRegistry.getAvailable(), this.model);
}

/**
* Resolve the explicit thinking suffix that should apply when a temporary
* picker selects a model already assigned to a configured role.
*/
resolveTemporaryModelThinkingLevel(model: Model): ConfiguredThinkingLevel | undefined {
const availableModels = this.#modelRegistry.getAvailable();
if (availableModels.length === 0) return undefined;

const matchPreferences = getModelMatchPreferences(this.settings);
for (const role of getKnownRoleIds(this.settings)) {
const roleValue = this.settings.getModelRole(role);
if (!roleValue) continue;

const resolved = resolveModelRoleValue(roleValue, availableModels, {
settings: this.settings,
matchPreferences,
});
if (!resolved.explicitThinkingLevel || resolved.thinkingLevel === undefined || !resolved.model) continue;
if (modelsAreEqual(resolved.model, model)) return resolved.thinkingLevel;
}

return undefined;
}

get promptTemplates(): ReadonlyArray<PromptTemplate> {
return this.#promptTemplates;
}
Expand Down
33 changes: 33 additions & 0 deletions packages/coding-agent/test/agent-session-role-thinking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe("AgentSession role model thinking behavior", () => {
initialModelId: string;
initialThinkingLevel: Effort;
modelRoles: Record<string, string>;
runtimeApiKeys?: Record<string, string>;
}) {
const model = getAnthropicModelOrThrow(options.initialModelId);
const agent = new Agent({
Expand All @@ -62,6 +63,10 @@ describe("AgentSession role model thinking behavior", () => {
const authStorage = await AuthStorage.create(path.join(tempDir.path(), "testauth.db"));
authStorages.push(authStorage);
authStorage.setRuntimeApiKey("anthropic", "test-key");
const runtimeApiKeys = options.runtimeApiKeys ?? {};
for (const provider in runtimeApiKeys) {
authStorage.setRuntimeApiKey(provider, runtimeApiKeys[provider]);
}
const modelRegistry = new ModelRegistry(authStorage, path.join(tempDir.path(), "models.yml"));

sessionSettings = Settings.isolated();
Expand Down Expand Up @@ -617,6 +622,34 @@ describe("AgentSession role model thinking behavior", () => {
expect(session.autoResolvedThinkingLevel()).toBeUndefined();
});

it("applies matching role thinking to temporary model picks", async () => {
const defaultModel = getAnthropicModelOrThrow("claude-sonnet-4-5");
const temporaryModel = getBundledModel("google-antigravity", "gemini-3.5-flash");
if (!temporaryModel) throw new Error("Expected google-antigravity model gemini-3.5-flash to exist");

await createSession({
initialModelId: defaultModel.id,
initialThinkingLevel: Effort.Low,
modelRoles: {
smol: `${temporaryModel.provider}/${temporaryModel.id}:high`,
},
runtimeApiKeys: {
[temporaryModel.provider]: "test-key",
},
});

const roleResolved = session.resolveRoleModelWithThinking("smol");
expect(roleResolved.model?.id).toBe(temporaryModel.id);
expect(roleResolved.thinkingLevel).toBe(Effort.High);

const roleThinkingLevel = session.resolveTemporaryModelThinkingLevel(temporaryModel);
await session.setModelTemporary(temporaryModel, roleThinkingLevel);

expect(session.model?.provider).toBe(temporaryModel.provider);
expect(session.model?.id).toBe(temporaryModel.id);
expect(session.thinkingLevel).toBe(Effort.High);
});

it("ignores a stale recorded role and cycles from the active model", async () => {
const defaultModel = getAnthropicModelOrThrow("claude-sonnet-4-5");
const slowModel = getAnthropicModelOrThrow("claude-sonnet-4-6");
Expand Down
Loading