import type { Api, Model, StreamOptions } from "../types"; import { AssistantMessageEventStream } from "./event-stream"; /** Stable lead phrase of the guard's error message; exported for tests. The * message also carries "stream stall" so the session + transport retry * classifiers treat it as a transient (retryable) stop without bespoke rules. */ export declare const THINKING_LOOP_ERROR_MARKER = "Thinking loop detected"; /** * True when `model` is a Gemini model whose native thinking stream surfaces the * "thought summary" titles this module's header guard counts. * * OpenAI-compat transports can serve Gemini under an arbitrary provider/id, so they * carry the explicit `compat.enableGeminiThinkingLoopGuard` flag; direct Gemini * transports carry a clearly shaped id/provider, so a string match is sufficient. */ export declare function isGeminiThinkingModel(model: Model): boolean; /** * True when `model` should be guarded for thinking/response loops (Gemini & DeepSeek). * * OpenAI-compat transports can serve Gemini or DeepSeek under an arbitrary provider/id. * Direct Gemini/DeepSeek transports carry a clearly shaped id/provider, so a string match * is sufficient. */ export declare function isLoopGuardedModel(model: Model, options?: StreamOptions): boolean; /** @deprecated Use isLoopGuardedModel instead. */ export declare function isGeminiThinkingLoopModel(model: Model): boolean; /** * Stateful detector fed the streamed thinking deltas. `push` returns a * human-readable reason the first time a loop shape is recognized; the caller * is responsible for stopping after the first hit. */ export declare class ThinkingLoopDetector { #private; push(delta: string): string | null; /** Process the buffered trailing paragraph (one with no blank-line / heading * terminator). Called when the thinking block ends so the final segment — * which may be the one that completes a duplicate cluster — is not dropped. */ flush(): string | null; } /** * Consecutive Gemini thought-summary headers in one uninterrupted reasoning * 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. 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 declare const GEMINI_HEADER_RUNAWAY_THRESHOLD = 24; /** * True when a single trimmed line is a Gemini reasoning-summary title: a markdown * ATX heading (`## …`) or a whole-line bold / bold-italic run (`**Title**`, * `***Title***`). Inline emphasis inside prose never matches — the bold run must * span the entire line. Mirrors the title shapes {@link ThinkingLoopDetector} * strips before similarity analysis. */ export declare function isReasoningSummaryHeader(line: string): boolean; /** * Counts consecutive Gemini reasoning-summary headers across a streamed thinking * block. {@link push} returns true exactly once — when the running header count * first reaches {@link GEMINI_HEADER_RUNAWAY_THRESHOLD} — and the caller then * interrupts the stream and reminds the model to issue a tool call. Paragraph * lines between titles do NOT reset the run (Gemini emits header + paragraph per * thought, so the run IS the number of summaries); leaving the reasoning channel * does, via {@link reset} on a new thinking block / prose / tool call. */ export declare class GeminiHeaderRunDetector { #private; /** Feed a thinking delta. Returns true the first time the run hits the threshold. */ push(delta: string): boolean; /** Number of summary titles counted in the current run (for the reminder/log). */ get count(): number; /** Re-arm for a fresh reasoning block: clears the buffer, count, and latch. */ reset(): void; } /** * Wrap a provider stream with the loop guard. `controller` is the guard's own * abort handle: aborting it (after wiring it into the provider's signal via * {@link withGeminiThinkingLoopGuard}) tears down the upstream once a loop * trips. */ export declare function guardThinkingLoopStream(inner: AssistantMessageEventStream, model: Model, controller: AbortController, options?: StreamOptions): AssistantMessageEventStream; /** * Apply the loop guard around a provider dispatch. For non-guarded models * (or when disabled) this is a transparent pass-through. For guarded models it injects a * guard abort signal into the provider call so a detected loop tears down the * upstream, then wraps the returned stream. The guard only raises the retryable * stall; bounding the re-samples and the final cook pass lives in the * result-awaiting caller. */ export declare function withGeminiThinkingLoopGuard(model: Model, options: O | undefined, dispatch: (options: O | undefined) => AssistantMessageEventStream): AssistantMessageEventStream;