import type { AgentToolUpdateCallback } from "@gajae-code/agent-core"; export declare const DEFAULT_MAX_LINES = 3000; export declare const DEFAULT_MAX_BYTES: number; export declare const DEFAULT_MAX_COLUMN = 1024; export declare const DEFAULT_ARTIFACT_MAX_BYTES: number; /** * Terminal publisher used when managed artifact storage exposes an ID but no * writable pathname. The sink supplies raw content retained up to its artifact * hard cap; `omittedBytes` is the source-byte count already omitted by that cap. * A successful publisher may report additional omitted bytes (for example, a * downstream storage cap); those counts are added to the sink summary. Failed * and unavailable results are surfaced through the bounded artifact diagnostic * without exposing an unresolvable artifact URL. */ export type TerminalArtifactPublishResult = { status: "published"; artifactId: string; omittedBytes?: number; } | { status: "unavailable"; } | { status: "failed"; diagnostic: string; }; export type TerminalArtifactPublisher = (content: string, info: { totalBytes: number; omittedBytes: number; }) => Promise; export interface OutputSummary { output: string; truncated: boolean; totalLines: number; totalBytes: number; outputLines: number; outputBytes: number; /** Bytes elided from the middle when head-retain mode is active. */ elidedBytes?: number; /** Lines elided from the middle when head-retain mode is active. */ elidedLines?: number; /** Bytes dropped by the per-line column cap (sum across all lines). */ columnDroppedBytes?: number; /** Number of distinct lines that hit the per-line column cap. */ columnTruncatedLines?: number; /** Artifact ID for internal URL access (artifact://) when output was persisted. */ artifactId?: string; /** Bytes omitted from artifact storage after the artifact hard cap was reached. */ artifactTruncatedBytes?: number; /** Bounded diagnostic when artifact writer or terminal publisher creation, write, finalization, or publication failed. */ artifactFailureDiagnostic?: string; } export interface OutputSinkOptions { /** * Deprecated managed artifact pathname. Bare paths are deliberately ignored: * streaming output must be terminally published through ArtifactManager. */ artifactPath?: string; artifactId?: string; /** * Optional terminal publisher for managed artifact stores that do not expose a * writable path. It is invoked only when visible spill/truncation requires an * artifact, and receives raw content bounded by `artifactMaxBytes`. */ artifactPublisher?: TerminalArtifactPublisher; /** Tail buffer budget (bytes). Default DEFAULT_MAX_BYTES. */ spillThreshold?: number; /** * When > 0, the sink keeps the first `headBytes` of output in addition to * the rolling tail window. Output between the two windows is elided * (middle elision). Default 0 = tail-only behavior. */ headBytes?: number; /** * Per-line byte cap. When > 0, lines wider than `maxColumns` bytes are * truncated with an ellipsis at write time; remaining bytes up to the next * `\n` are dropped. Cap state persists across chunks so split-mid-line * writes still respect the budget. Default 0 = no per-line cap. */ maxColumns?: number; /** Hard cap for artifact writes/pending replay. Default DEFAULT_ARTIFACT_MAX_BYTES. */ artifactMaxBytes?: number; onChunk?: (chunk: string) => void; /** Minimum ms between onChunk calls. 0 = every chunk (default). */ chunkThrottleMs?: number; /** * Unthrottled per-chunk callback fired *after* sanitization but *before* * any throttle gating, column capping, or head/tail bookkeeping. Used by * background-job substrate to record the complete process stream for the * Monitor tool while keeping `onChunk` cheap for UI/progress. * * Receives the sanitized chunk verbatim; never receives the column-capped * or minimized text. Implementations must be fast and side-effect-free * relative to the sink (the sink does not catch errors from this callback). */ onRawChunk?: (chunk: string) => void; /** * Opt-in (F21): when true, sanitization + live callback delivery + retention are coalesced over * batched raw chunks instead of run per chunk, bounding sync CPU for many-small-chunk output. The * raw artifact mirror stays byte-correct. Defaults to the PI_OUTPUT_SANITIZE_COALESCE env flag * (default OFF — the per-chunk path is byte-identical to historical behavior). */ coalesceSanitize?: boolean; } export interface TruncationResult { content: string; truncated?: boolean; truncatedBy?: "lines" | "bytes" | "middle"; totalLines: number; totalBytes: number; outputLines?: number; outputBytes?: number; /** Bytes elided from the middle (truncateMiddle only). */ elidedBytes?: number; /** Lines elided from the middle (truncateMiddle only). */ elidedLines?: number; lastLinePartial?: boolean; firstLineExceedsLimit?: boolean; lastLineExceedsLimit?: boolean; } /** Direction vocabulary used by callers that select which end of content to retain. */ export type TruncationDirection = "head" | "last" | "both"; export interface TruncationOptions { /** Maximum number of lines (default: 3000) */ maxLines?: number; /** Maximum number of bytes (default: 50KB) */ maxBytes?: number; /** * For `truncateMiddle`: bytes reserved for the head window. The tail * window receives `maxBytes - maxHeadBytes`. Default `floor(maxBytes/2)`. */ maxHeadBytes?: number; /** * For `truncateMiddle`: lines reserved for the head window. The tail * window receives `maxLines - maxHeadLines`. Default `floor(maxLines/2)`. */ maxHeadLines?: number; /** Direction to dispatch through truncateContent (head, last, or both). */ direction?: TruncationDirection; } /** Result from byte-level truncation helpers. */ export interface ByteTruncationResult { text: string; bytes: number; } export interface TailTruncationNoticeOptions { fullOutputPath?: string; originalContent?: string; suffix?: string; } export interface HeadTruncationNoticeOptions { startLine?: number; totalFileLines?: number; } /** * Truncate a string/buffer to fit within a byte limit, keeping the tail. * Handles multi-byte UTF-8 boundaries correctly. */ export declare function truncateTailBytes(data: string | Uint8Array, maxBytes: number): ByteTruncationResult; /** * Truncate a string/buffer to fit within a byte limit, keeping the head. * Handles multi-byte UTF-8 boundaries correctly. */ export declare function truncateHeadBytes(data: string | Uint8Array, maxBytes: number): ByteTruncationResult; /** * Truncate a single line to max characters, appending '…' if truncated. */ export declare function truncateLine(line: string, maxChars?: number): { text: string; wasTruncated: boolean; }; /** Shared helper to build a no-truncation result. */ export declare function noTruncResult(content: string, totalLines?: number, totalBytes?: number): TruncationResult; /** * Truncate content from the head (keep first N lines/bytes). * Never returns partial lines. If the first line exceeds the byte limit, * returns empty content with firstLineExceedsLimit=true. * * This implementation avoids Buffer.from(content) for the whole input. * It only computes UTF-8 byteLength for candidate lines that can still fit. */ export declare function truncateHead(content: string, options?: TruncationOptions): TruncationResult; /** * Truncate content from the tail (keep last N lines/bytes). * May return a partial first line if the last line exceeds the byte limit. * * Also avoids Buffer.from(content) for the whole input. */ export declare function truncateTail(content: string, options?: TruncationOptions): TruncationResult; /** * Format the inline marker substituted for the elided middle region. * Returned without surrounding newlines so callers can position it freely. */ export declare function formatMiddleElisionMarker(elidedLines: number, elidedBytes: number): string; /** * A retained source segment returned by {@link truncateMiddleWindows}. * Line coordinates are 1-indexed and inclusive. */ export type ReadSegment = { kind: "lines"; content: string; lines: number; bytes: number; origin: { startLine: number; endLine: number; }; lastLinePartial: false; } | { kind: "partial-line"; content: string; lines: 1; bytes: number; origin: { startLine: number; endLine: number; }; sourceLineBytes: number; lastLinePartial: true; }; /** The actual windows retained by middle truncation. */ export interface ReadWindow { kind: "full" | "head-only" | "tail-only" | "middle"; head?: ReadSegment; tail?: ReadSegment; overlap: "disjoint" | "adjacent" | "overlapping"; elidedLines: number; elidedBytes: number; totalLines: number; totalBytes: number; /** @deprecated Non-enumerable compatibility reason for older consumers. */ truncatedBy?: "lines" | "bytes" | "middle"; } /** * Truncate content while exposing the exact retained head/tail windows. * * The budget split and fallback ordering intentionally mirror truncateMiddle. * The overlap classification is performed before resolving an overlap to a * full window, so callers can distinguish disjoint, adjacent and overlapping * candidate windows even when no marker is needed. */ export declare function truncateMiddleWindows(content: string, options?: TruncationOptions): ReadWindow; /** * Truncate content keeping a head window and a tail window, eliding the middle. * The composed return shape remains field-for-field compatible with the * historical implementation; callers that need coordinates use * truncateMiddleWindows directly. */ export declare function truncateMiddle(content: string, options?: TruncationOptions): TruncationResult; /** * Dispatch truncation using the caller-facing direction vocabulary. */ export declare function truncateContent(content: string, options?: TruncationOptions): TruncationResult; export declare class TailBuffer { #private; readonly maxBytes: number; constructor(maxBytes: number); append(text: string): void; text(): string; bytes(): number; } export declare class OutputSink { #private; constructor(options?: OutputSinkOptions); /** * Push a chunk of output. Raw bytes are mirrored to artifacts, while the * visible retention windows are selected from the sanitized/column-capped * stream so production-default display matches the historical processed view. */ push(chunk: string): void; createInput(): WritableStream; /** * Replace the in-memory buffer with the given text. Used when an upstream * minimizer rewrites the captured output after the raw bytes have already * been streamed. * * After this call the replacement is authoritative: counters reflect its full * size, the configured head+tail byte windows are retained with UTF-8-safe * boundaries, and future head accumulation is disabled so later `push()` calls * append directly to the tail without reordering the replacement. */ replace(text: string): void; dump(notice?: string): Promise; } /** * Format a truncation notice for tail-truncated output (bash, python, ssh). * Returns empty string if not truncated. */ export declare function formatTailTruncationNotice(truncation: TruncationResult, options?: TailTruncationNoticeOptions): string; /** * Format a truncation notice for head-truncated output (read tool). * Returns empty string if not truncated. */ export declare function formatHeadTruncationNotice(truncation: TruncationResult, options?: HeadTruncationNoticeOptions): string; /** * Build an onChunk handler that appends to a TailBuffer and emits a streaming * update (when `onUpdate` is defined) with the buffer's current text. */ export declare function streamTailUpdates(tailBuffer: TailBuffer, onUpdate: AgentToolUpdateCallback | undefined): (chunk: string) => void;