@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
55 lines (54 loc) • 3.1 kB
TypeScript
/**
* In-turn context guard for Gemini-shaped agent loops (native Vertex + AI
* Studio + Gemini 3).
*
* These loops already had `createContextGuard`, but it is **stop-only**: once
* the projected prompt crosses the threshold it breaks the loop and synthesizes
* an answer from whatever it has. That avoids a provider rejection, but it also
* ends the turn early — the model stops doing work it was mid-way through.
*
* This module brings them to parity with the other loops: reclaim budget and
* CONTINUE, falling back to the existing stop only when reclaiming cannot get
* back under the line. The reclaim policy is shared with every other provider
* via `loopGuardCore`; this module owns only the Gemini shape mapping.
*
* Gemini history is `{ role, parts[] }`, where a part is a `functionCall`
* (tool invocation) or `functionResponse` (its result). One model turn can
* carry several `functionCall` parts and the following user turn carries the
* matching `functionResponse` parts, so the CONTENT is the batch unit —
* dropping a call turn together with its response turn can never orphan a part,
* which Gemini rejects.
*/
import type { GeminiGuardContent, LoopGuardPlan } from "../types/index.js";
/** Marker left where dropped history used to be. */
export declare const GEMINI_ELISION_NOTE = "[Earlier tool exchanges were removed to fit the context window.]";
/** True when this content carries tool results worth previewing. */
export declare function isGeminiToolResponseContent(content: GeminiGuardContent): boolean;
/** Head/tail preview for an oversized tool response payload. */
export declare function previewGeminiToolResponseText(text: string): string;
/**
* Decide what to reclaim from a Gemini agent loop.
*
* Returns `undefined` when the history still fits, in which case the caller
* must leave it byte-identical — any rewrite invalidates the provider's cached
* prefix, so "no change" has to mean no change.
*
* `observedPromptTokens` must be a count for the payload ABOUT TO BE SENT —
* every Gemini-shaped loop plans only when its `createContextGuard` trips, and
* that guard's `projectedNextPromptTokens` is exactly that: the provider's real
* count for the last request plus the growth measured since. Dividing it by
* this module's estimate of the current history therefore compares two views of
* one payload, which is what makes the correction meaningful. A count for an
* EARLIER request must not be passed here: the loop has appended a model turn
* and its tool turn since, so the denominator would always be the larger of the
* two, the ratio would read below 1 and the `Math.max(1, …)` floor would pin
* calibration at 1 — silently disabling the correction. `planAnthropicLoopReclaim`
* carries `previousSentEstimate` for callers in that other position.
*/
export declare function planGeminiLoopReclaim(args: {
contents: readonly GeminiGuardContent[];
availableInputTokens: number;
fixedOverheadTokens?: number;
provider?: string;
observedPromptTokens?: number;
}): LoopGuardPlan | undefined;