Merge PR #5297: fix(coding-agent): respect role thinking for temporary model picks (@roboomp)
# Conflicts: # packages/coding-agent/src/modes/controllers/selector-controller.ts
This commit is contained in:
@@ -94,6 +94,7 @@
|
||||
- Fixed tab reuse issues where hung navigation or unhandled modals would cause initialization to stall and trigger a force-kill
|
||||
- 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
|
||||
|
||||
|
||||
@@ -634,7 +634,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";
|
||||
@@ -777,6 +778,7 @@ export class SelectorController {
|
||||
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
},
|
||||
|
||||
onLoginRequest: providerId => {
|
||||
done();
|
||||
void this.#loginThenReopenModelHub(providerId);
|
||||
|
||||
@@ -187,7 +187,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";
|
||||
@@ -7788,6 +7788,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;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
@@ -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();
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user