feat(ai/utils): increased the Gemini header runaway threshold

- Raised `GEMINI_HEADER_RUNAWAY_THRESHOLD` from 10 to 24 to avoid false-positive interrupts on legitimate, complex reasoning blocks.
- Added a regression test verifying that 10 distinct, progressing headers do not trip the detector while 24 headers still trigger it.
This commit is contained in:
can1357
2026-06-30 20:26:51 +02:00
parent f1453d72ae
commit 490662ab2e
4 changed files with 21 additions and 7 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Raised Gemini header runaway threshold to prevent premature interruption of complex reasoning loops
## [16.2.9] - 2026-06-30
### Added
+10 -5
View File
@@ -287,12 +287,17 @@ export class ThinkingLoopDetector {
* stream that trips the tool-call reminder. Gemini occasionally narrates a long
* chain of titled summaries ("Examining Result Handling", "Refining Result
* Rendering", …) without ever calling a tool, burning the whole budget on
* planning; at this many distinct titles it has almost certainly stalled. This
* is the over-planning shape {@link ThinkingLoopDetector} misses — those titles
* are stripped before its similarity analysis precisely because their wording
* keeps changing, so a genuinely-distinct planning runaway never trips it.
* planning. This is the over-planning shape {@link ThinkingLoopDetector} misses —
* those titles are stripped before its similarity analysis precisely because their
* wording keeps changing, so a genuinely-distinct planning runaway never trips it.
*
* Set well above legitimate hard-problem depth: a capable model can emit ~10
* distinct, progressing hypotheses in a single reasoning block before acting (and
* a false trip is costly — the interrupt discards the whole reasoning turn). A
* real narration runaway burns dozens-to-hundreds of titles, so this still trips
* fast on the actual pathology.
*/
export const GEMINI_HEADER_RUNAWAY_THRESHOLD = 10;
export const GEMINI_HEADER_RUNAWAY_THRESHOLD = 24;
/**
* True when a single trimmed line is a Gemini reasoning-summary title: a markdown
+6
View File
@@ -615,6 +615,12 @@ describe("GeminiHeaderRunDetector", () => {
expect(feedHeaders(runaway)).toBe(true);
});
test("does not trip on a legitimate 10-header debugging block (regression)", () => {
// A real, productive debugging stretch emitted 10 distinct progressing headers; never interrupt that.
expect(feedHeaders(distinctPlanningRunaway(10))).toBe(false);
expect(feedHeaders(distinctPlanningRunaway(24))).toBe(true);
});
test("counts headers across intervening paragraphs (one summary = one header)", () => {
const detector = new GeminiHeaderRunDetector();
let tripped = false;
+1 -2
View File
@@ -15,7 +15,6 @@ import type { OAuthAccess } from "@oh-my-pi/pi-ai/auth-storage";
import type { OAuthProvider } from "@oh-my-pi/pi-ai/oauth/types";
import { getGitLabDuoModels } from "@oh-my-pi/pi-ai/providers/gitlab-duo";
import { $env } from "@oh-my-pi/pi-utils";
import { buildCompat } from "../src/build";
import { ANTIGRAVITY_PRIMARY_ENDPOINT, fetchAntigravityDiscoveryModels } from "../src/discovery/antigravity";
import { fetchCodexModels } from "../src/discovery/codex";
import { buildGitLabDuoWorkflowFallbackModel } from "../src/discovery/gitlab-duo-workflow";
@@ -645,7 +644,7 @@ Model Statistics:`);
function canonicalizeModelCompat(model: ModelSpec<Api>): void {
if (!model.compat) return;
if (model.compat.disableStrictTools === false) {
if ("disableStrictTools" in model.compat && model.compat.disableStrictTools === false) {
delete model.compat.disableStrictTools;
}