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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user