fix(session): preserved auto-thinking level on classifier failure

- Kept the last successfully classified effort when a later classification fails.
- Added regression coverage for the success-then-failure transition.

Fixes #6877
This commit is contained in:
roboomp
2026-07-28 08:06:17 +00:00
parent d16c6168c8
commit 5d9cbf19e6
3 changed files with 36 additions and 4 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Preserved the active auto-thinking effort when per-turn classification fails, avoiding an unnecessary full prompt-cache invalidation from reverting to the model's provisional default ([#6877](https://github.com/can1357/oh-my-pi/issues/6877)).
## [17.1.7] - 2026-07-27
### Fixed
@@ -581,9 +581,9 @@ export class ModelControls {
/**
* Classify the current user turn and set the effective thinking level for it.
* Bounded by a timeout + abort; on any failure (no smol model, timeout, parse
* error) it falls back to the provisional concrete level and continues. Never
* throws into the turn, and never clears `#autoThinking` (auto stays active).
* Bounded by a timeout + abort; on failure it preserves the last classified
* level, or uses the provisional concrete level before the first resolution.
* Never throws into the turn, and never clears `#autoThinking`.
*/
async applyAutoThinkingLevel(promptText: string, generation: number): Promise<void> {
const model = this.#model;
@@ -625,7 +625,7 @@ export class ModelControls {
const effort = clampThinkingLevelToCeiling(
model,
resolved ?? resolveProvisionalAutoLevel(model),
resolved ?? this.#autoResolvedLevel ?? resolveProvisionalAutoLevel(model),
this.#thinkingLevelCeiling,
);
if (effort === undefined) return;
@@ -541,6 +541,34 @@ describe("AgentSession role model thinking behavior", () => {
);
});
it("preserves the resolved auto level when a later classification fails", async () => {
const model = getAnthropicModelOrThrow("claude-sonnet-4-5");
await createSession({
initialModelId: model.id,
initialThinkingLevel: Effort.High,
modelRoles: { default: `${model.provider}/${model.id}` },
});
vi.spyOn(session.agent, "prompt").mockResolvedValue(undefined);
vi.spyOn(autoThinkingClassifier, "classifyDifficulty")
.mockResolvedValueOnce(Effort.Low)
.mockRejectedValueOnce(new Error("classifier down"));
session.setThinkingLevel(AUTO_THINKING);
await session.prompt("Handle a straightforward update");
const receiptCount = session.sessionManager
.getEntries()
.filter(entry => entry.type === "thinking_level_change").length;
await session.prompt("Investigate another update");
expect(session.configuredThinkingLevel()).toBe(AUTO_THINKING);
expect(session.thinkingLevel).toBe(Effort.Low);
expect(session.autoResolvedThinkingLevel()).toBe(Effort.Low);
expect(session.agent.state.thinkingLevel).toBe(Effort.Low);
expect(session.sessionManager.getEntries().filter(entry => entry.type === "thinking_level_change")).toHaveLength(
receiptCount,
);
});
it("skips classification for synthetic turns", async () => {
const model = getAnthropicModelOrThrow("claude-sonnet-4-5");
await createSession({