/** * M4 — ACP nudge: the kernel's compression recommendation, rendered as an * injected user message with a seq-based compressible-range table (D1: * "seq is the ref" — DSH has no in-memory message rewrite hook, so the model * targets ranges by surface seq rather than by tags). * @module billion-context-dsh/nudge */ import { type CompressionCore, type CompressionState, type ContextBreakdown, type CoreMessage, type NudgeDecision } from 'acp-kernel'; import { type UserMessage } from '@deepseek-ai/dsh-llm'; import type { Agent } from '@deepseek-ai/dsh-agent'; import { AcpStateStore } from './state.ts'; import { type KernelRangeView, type MediaPriceOf } from './region.ts'; import { type KernelConfigInput } from './config.ts'; import { type ResolvedPrompts } from './prompts.ts'; export declare function stripNudgeGuidance(text: string): string; /** Kernel inputs the nudge path shares with the compress tool. */ export interface NudgeEnvironment extends KernelConfigInput { readonly kernel: CompressionCore; readonly store: AcpStateStore; /** Resolved prompt templates (optional: falls back to DEFAULT_RESOLVED). */ readonly prompts?: ResolvedPrompts; } export interface NudgeOutcome { readonly message: UserMessage; readonly emergency: boolean; } /** * Resolve the best available token count for ACP pressure decisions. * * Priority chain: * 1. `sessionProjections.contextPressure.projectedTokens` — matches the UI's * context-occupancy display (includes fixed overhead: system prompt, tool * definitions, AGENTS.md, etc.). Provider-anchored; reacts to compaction. * 2. `tokenMeter.measure(session).surfaceTokens` — heuristic surface-only * estimate (pure conversation messages, no fixed overhead). Falls back * when sessionProjections is unavailable or has no provider anchor yet. * 3. `defaultCountTokens` character heuristic — last resort for tests and * minimal hosts that lack the token-meter service. */ export declare function resolveTokenCount(agent: Agent, coreMessages: CoreMessage[]): number; /** * Lazy per-seq media price. The FIRST lookup triggers one meter measurement, so * the range walk only asks about seqs that really carry an attachment — a * media-free session never pays for the measurement (issue #117, issue #110). * Exported for the context-overflow recovery path, which ranks ranges with the * same vocabulary as the nudge table. */ export declare function meterMediaPriceResolver(agent: Agent, session: import('@deepseek-ai/dsh-session').Session): MediaPriceOf; /** * Render the compressible-range table as seq refs for the model. * * The spans are the kernel's own (`compressibleRanges`, translated to surface * seqs) with the host guards applied on top — see buildCompressibleSeqRanges. * This function used to self-compute them from the surface as a labeled * `UPSTREAM:` workaround for kernel ref-map drift; that drift is fixed upstream * (acp-kernel #207) and the workaround is gone (rule 11). */ export declare function rangeTable(session: import('@deepseek-ai/dsh-session').Session, kernelView: KernelRangeView, prompts?: ResolvedPrompts, mediaPriceOf?: MediaPriceOf): string; /** * Compute a SURFACE-ONLY context breakdown for display, aligned with * `acp_status` (kernel `buildStatusReport`/`renderOverview`). * * The kernel's own `computeContextBreakdown` (which the nudge text renders) * walks the message array it is fed — and `buildNudge` feeds it the FULL log * (`allLogMessages`, needed so T2/T3 distillation can anchor every block). So * a session with compressed blocks reports HISTORICAL totals there: every * original tool/text message already absorbed into a block is counted again, * e.g. `85.2K tool` for ~8.5K of live tool context. `acp_status` instead feeds * `buildStatusReport` the VISIBLE surface + active-block summaries, so its * breakdown reads the true current context. This function reproduces that * visible-surface reality for the nudge line so the two tools agree. * * Classification replicates kernel `computeContextBreakdown` (tool-call/ * tool-result → tool, `system` role → system, `` code `` fence in text → * code, else text) EXCEPT summaries: kernel detects summaries by a * `[Compressed conversation section]` text prefix, which never matches a DSH * checkpoint node (our summary is the plain summary + `compactCheckpointSource` * source marker). We instead count active-block summaries directly from kernel * state (same source `buildStatusReport` uses), and the caller must exclude * checkpoint summary nodes from `messages` (they are not in any block's * `effectiveMessageIds` and would double-count — mirror of `/acp-prune` status's * `isCheckpointNode` exclusion). */ export declare function computeSurfaceBreakdown(state: CompressionState, messages: readonly CoreMessage[], total: number, growth: number): ContextBreakdown; /** * Max emergency nudge injections within a single user turn. Bounds the * positive-feedback loop where an unrelieved ≥emergency-threshold pressure * re-injects a durable emergency nudge on every pre-step forever (issue #108). * Mirrors billion-context-pi commit 414acd1 (cap emergency nudge injections per * user turn). Normal-pressure nudges remain limited to one per turn regardless. */ export declare const EMERGENCY_NUDGE_MAX_PER_TURN = 3; /** * Decide and build one nudge message for the agent's next pre-step. Returns * null when the kernel recommends no nudge or the per-turn budget is spent: * normal-pressure nudges fire at most once per user turn, and emergency nudges * are capped at {@link EMERGENCY_NUDGE_MAX_PER_TURN} per user turn so an * unrelieved ≥threshold pressure cannot re-inject a durable nudge on every * pre-step forever (issue #108). Also advances the in-memory kernel state (ref * assignment) so the compress tool can resolve seq → mNNNNN refs. * * `onEmergencyCapHit` (optional) fires when the kernel still wants an * emergency nudge but the per-turn budget is spent — the host uses it to log * why the model stops receiving nudges (issue #108 review). */ export declare function buildNudge(agent: Agent, env: NudgeEnvironment, lastNudgeTurn: Map, emergencyNudges: Map, onEmergencyCapHit?: () => void): NudgeOutcome | null; /** * Render the nudge message text. DEFAULT (no `config.prompts.nudge` override) * calls the kernel's own `renderNudgeText` — EFFICIENCY_NOTE/EMERGENCY_HEADER, * context breakdown, HOW_TO_COMPRESS_RULES, tier rules, and the batch tip all * come from acp-kernel verbatim (the kernel-alignment principle). Only the * ref-ID-oriented segments are replaced with our seq-based equivalents, * because DSH has no `` ref tags — see docs/dsh-porting-verification.md: * - `rangesStr` (mNNNNN refs) → the surface-seq range table; * - the emergency JSON example (startId/endId) → a seq example; * - the tier trigger block (block ids bN) → our tier line with surface seqs. * When a host overrides any `prompts.nudge` slot, the template path is used so * `config.prompts` keeps full control (custom copy wins over kernel defaults). */ export declare function buildNudgeText(nudge: NudgeDecision, emergency: boolean, session: import('@deepseek-ai/dsh-session').Session, kernelView: KernelRangeView, prompts?: ResolvedPrompts, mediaPriceOf?: MediaPriceOf): string;