@linxiraos/pi-agent-core 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +21 -5
  2. package/dist/types/agent-loop.d.ts +137 -0
  3. package/dist/types/agent.d.ts +465 -0
  4. package/dist/types/append-only-context.d.ts +131 -0
  5. package/dist/types/compaction/branch-summarization.d.ts +101 -0
  6. package/dist/types/compaction/compaction-v2-streaming.d.ts +85 -0
  7. package/dist/types/compaction/compaction.d.ts +321 -0
  8. package/dist/types/compaction/entries.d.ts +111 -0
  9. package/dist/types/compaction/errors.d.ts +38 -0
  10. package/dist/types/compaction/index.d.ts +13 -0
  11. package/dist/types/compaction/message-cache.d.ts +23 -0
  12. package/dist/types/compaction/messages.d.ts +79 -0
  13. package/dist/types/compaction/openai.d.ts +117 -0
  14. package/dist/types/compaction/pruning.d.ts +105 -0
  15. package/dist/types/compaction/shake.d.ts +99 -0
  16. package/dist/types/compaction/tool-protection.d.ts +19 -0
  17. package/dist/types/compaction/utils.d.ts +61 -0
  18. package/dist/types/compaction.d.ts +1 -0
  19. package/dist/types/index.d.ts +13 -0
  20. package/dist/types/pause.d.ts +46 -0
  21. package/dist/types/proxy.d.ts +91 -0
  22. package/dist/types/replay-policy.d.ts +5 -0
  23. package/dist/types/run-collector.d.ts +196 -0
  24. package/dist/types/telemetry.d.ts +625 -0
  25. package/dist/types/thinking.d.ts +18 -0
  26. package/dist/types/tokenizer.d.ts +2 -0
  27. package/dist/types/types.d.ts +786 -0
  28. package/dist/types/utils/yield.d.ts +71 -0
  29. package/package.json +79 -78
  30. package/src/compaction/branch-summarization.ts +12 -6
  31. package/src/compaction/compaction-v2-streaming.ts +1 -0
  32. package/src/compaction/compaction.ts +48 -6
  33. package/src/telemetry.ts +39 -4
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Append-only context mode — stabilizes the byte prefix sent to the LLM
3
+ * across turns so provider prefix caches (DeepSeek, Anthropic, etc.)
4
+ * hit at the maximum possible rate.
5
+ *
6
+ * Two mechanisms:
7
+ *
8
+ * 1. **StablePrefix** — system prompt + tool specs are computed once
9
+ * and frozen. Subsequent turns reuse the exact same byte sequence
10
+ * unless `invalidate()` is called (e.g. after MCP reconnect).
11
+ *
12
+ * 2. **AppendOnlyLog** — messages only grow; prior turns are never
13
+ * re-serialized. Combined with a stable prefix, only the user's new
14
+ * message delta is a cache miss each turn.
15
+ */
16
+ import type { Context, Message, Tool } from "@linxiraos/pi-ai";
17
+ import type { AgentContext } from "./types.js";
18
+ /** Frozen system prompt + tool spec snapshot. */
19
+ export interface StablePrefixSnapshot {
20
+ systemPrompt: string[];
21
+ tools: Tool[];
22
+ fingerprint: string;
23
+ }
24
+ /** Options threaded through `build()` so the snapshot reflects loop-time settings. */
25
+ export interface BuildOptions {
26
+ /** Inject the `i` intent field into tool schemas (must match agent-loop's normalizeTools). */
27
+ intentTracing: boolean;
28
+ /** Strip tool descriptions from the provider-bound specs (must match normalizeTools). */
29
+ pruneToolDescriptions?: boolean;
30
+ }
31
+ /**
32
+ * A frozen prefix (system prompt + tools) that produces stable byte
33
+ * sequences across `build()` calls.
34
+ *
35
+ * The first `build()` snapshots the live state. Subsequent calls reuse
36
+ * the cached copy until `invalidate()` is called or the live state's
37
+ * fingerprint changes.
38
+ */
39
+ export declare class StablePrefix {
40
+ #private;
41
+ get fingerprint(): string;
42
+ get version(): number;
43
+ get built(): boolean;
44
+ /**
45
+ * Build or rebuild from live context.
46
+ * Returns `true` if the prefix actually changed (cache miss imminent).
47
+ */
48
+ build(context: AgentContext, options: BuildOptions): boolean;
49
+ /** Force rebuild on the next `build()` call. */
50
+ invalidate(): void;
51
+ /**
52
+ * Returns the cached prefix.
53
+ * @throws if `build()` was never called.
54
+ */
55
+ toContext(): {
56
+ systemPrompt: string[];
57
+ tools: Tool[];
58
+ };
59
+ }
60
+ /**
61
+ * Append-only message log at the `Message[]` (provider-level) layer.
62
+ *
63
+ * The only mutation path is `replaceTail()`, reserved for compaction.
64
+ * Every other operation is append-only.
65
+ */
66
+ export declare class AppendOnlyLog {
67
+ #private;
68
+ get length(): number;
69
+ append(message: any): void;
70
+ extend(messages: any[]): void;
71
+ /** Replace the last entry — only legal for compaction. */
72
+ replaceTail(replacement: any): void;
73
+ /** Returns a shallow copy of all entries. */
74
+ toMessages(): Message[];
75
+ /** Direct readonly access for in-place inspection. */
76
+ entries(): readonly Message[];
77
+ /** Drop entries past index `count`, keeping the first `count` byte-stable.
78
+ * Used by {@link AppendOnlyContextManager.syncMessages} to preserve the
79
+ * already-on-the-wire prefix when a later message diverges. */
80
+ truncate(count: number): void;
81
+ clear(): void;
82
+ }
83
+ /**
84
+ * Manages a stable prefix + append-only log for the agent loop.
85
+ *
86
+ * Call `build(context)` each turn to get a `Context` with stable
87
+ * `systemPrompt` and `tools` and append-only messages. Call
88
+ * `syncMessages(normalizedMessages)` after `convertToLlm` each
89
+ * turn to keep the log in sync.
90
+ *
91
+ * Example:
92
+ * ```
93
+ * const mgr = new AppendOnlyContextManager();
94
+ * const ctx = mgr.build(context); // first call snapshots prefix
95
+ * mgr.syncMessages(normalized); // grow the log
96
+ * ctx = mgr.build(context); // subsequent calls use cache
97
+ * ```
98
+ */
99
+ export declare class AppendOnlyContextManager {
100
+ #private;
101
+ readonly prefix: StablePrefix;
102
+ readonly log: AppendOnlyLog;
103
+ build(context: AgentContext, options: BuildOptions): Context;
104
+ /**
105
+ * Sync normalized (provider-level) messages into the append-only log.
106
+ *
107
+ * Three cases:
108
+ *
109
+ * 1. **Append**: same prefix, new tail → push the new entries.
110
+ * 2. **Compaction**: shorter array → clear the log and replay.
111
+ * 3. **In-place rewrite** (per-turn pruning, transformContext re-render,
112
+ * image strip, etc.): find the longest byte-stable prefix between
113
+ * the previously-synced messages and the new ones, drop the log
114
+ * down to that prefix, then append the diverged tail. Earlier
115
+ * revisions cleared the whole log on any digest change, which on
116
+ * llama.cpp / local backends forced a full ~40k-token re-prefill
117
+ * every turn that an extension, prune pass, or steering re-wrap
118
+ * rewrote a single message (#3406). Preserving the stable prefix
119
+ * lets the provider's KV cache stay warm up to the divergence
120
+ * point — the model only re-prefills from the changed message on.
121
+ */
122
+ syncMessages(normalizedMessages: any[]): void;
123
+ /** Reset prefix + log for a model/provider switch while mode stays active. */
124
+ invalidateForModelChange(): void;
125
+ /** Reset the sync cursor AND clear the log. */
126
+ resetSyncCursor(): void;
127
+ appendMessage(message: any): void;
128
+ replaceTailMessage(message: any): void;
129
+ invalidate(): void;
130
+ reset(context: AgentContext, options: BuildOptions): void;
131
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Branch summarization for tree navigation.
3
+ *
4
+ * When navigating to a different point in the session tree, this generates
5
+ * a summary of the branch being left so context isn't lost.
6
+ */
7
+ import type { Api, ApiKey, AssistantMessage, Context, Model, SimpleStreamOptions } from "@linxiraos/pi-ai";
8
+ import { type AgentTelemetry } from "../telemetry.js";
9
+ import type { AgentMessage } from "../types.js";
10
+ import type { ReadonlySessionManager, SessionEntry } from "./entries.js";
11
+ import { type ConvertToLlm } from "./messages.js";
12
+ import { type FileOperations } from "./utils.js";
13
+ export interface BranchSummaryResult {
14
+ summary?: string;
15
+ readFiles?: string[];
16
+ modifiedFiles?: string[];
17
+ aborted?: boolean;
18
+ error?: string;
19
+ }
20
+ /** Details stored in BranchSummaryEntry.details for file tracking */
21
+ export interface BranchSummaryDetails {
22
+ readFiles: string[];
23
+ modifiedFiles: string[];
24
+ }
25
+ export type { FileOperations } from "./utils.js";
26
+ export interface BranchPreparation {
27
+ /** Messages extracted for summarization, in chronological order */
28
+ messages: AgentMessage[];
29
+ /** File operations extracted from tool calls */
30
+ fileOps: FileOperations;
31
+ /** Total estimated tokens in messages */
32
+ totalTokens: number;
33
+ }
34
+ export interface CollectEntriesResult {
35
+ /** Entries to summarize, in chronological order */
36
+ entries: SessionEntry[];
37
+ /** Common ancestor between old and new position, if any */
38
+ commonAncestorId: string | null;
39
+ }
40
+ export interface GenerateBranchSummaryOptions {
41
+ /** Model to use for summarization */
42
+ model: Model;
43
+ /** API key for the model */
44
+ apiKey: ApiKey;
45
+ /** Abort signal for cancellation */
46
+ signal: AbortSignal;
47
+ /** Optional custom instructions for summarization */
48
+ customInstructions?: string;
49
+ /** Tokens reserved for prompt + LLM response (default 16384) */
50
+ reserveTokens?: number;
51
+ /** Optional metadata forwarded to the underlying API request (e.g. user_id for session attribution). */
52
+ metadata?: Record<string, unknown>;
53
+ /** Convert app-specific messages before serializing the branch summary prompt. */
54
+ convertToLlm?: ConvertToLlm;
55
+ /**
56
+ * Optional telemetry handle. When provided, the branch summary LLM call is
57
+ * wrapped in an OTEL chat span tagged with `pi.gen_ai.oneshot.kind = "branch_summary"`.
58
+ */
59
+ telemetry?: AgentTelemetry;
60
+ /**
61
+ * Optional completion transport override (same contract as
62
+ * {@link SummaryOptions.completeImpl}). Lets the host route the branch
63
+ * summary HTTP request through its provider-concurrency limiter instead
64
+ * of the default `completeSimple` transport.
65
+ */
66
+ completeImpl?: <TApi extends Api>(model: Model<TApi>, ctx: Context, options: SimpleStreamOptions) => Promise<AssistantMessage>;
67
+ }
68
+ /**
69
+ * Collect entries that should be summarized when navigating from one position to another.
70
+ *
71
+ * Walks from oldLeafId back to the common ancestor with targetId, collecting entries
72
+ * along the way. Does NOT stop at compaction boundaries - those are included and their
73
+ * summaries become context.
74
+ *
75
+ * @param session - Session manager (read-only access)
76
+ * @param oldLeafId - Current position (where we're navigating from)
77
+ * @param targetId - Target position (where we're navigating to)
78
+ * @returns Entries to summarize and the common ancestor
79
+ */
80
+ export declare function collectEntriesForBranchSummary(session: ReadonlySessionManager, oldLeafId: string | null, targetId: string): CollectEntriesResult;
81
+ /**
82
+ * Prepare entries for summarization with token budget.
83
+ *
84
+ * Walks entries from NEWEST to OLDEST, adding messages until we hit the token budget.
85
+ * This ensures we keep the most recent context when the branch is too long.
86
+ *
87
+ * Also collects file operations from:
88
+ * - Tool calls in assistant messages
89
+ * - Existing branch_summary entries' details (for cumulative tracking)
90
+ *
91
+ * @param entries - Entries in chronological order
92
+ * @param tokenBudget - Maximum tokens to include (0 = no limit)
93
+ */
94
+ export declare function prepareBranchEntries(entries: SessionEntry[], tokenBudget?: number): BranchPreparation;
95
+ /**
96
+ * Generate a summary of abandoned branch entries.
97
+ *
98
+ * @param entries - Session entries to summarize (chronological order)
99
+ * @param options - Generation options
100
+ */
101
+ export declare function generateBranchSummary(entries: SessionEntry[], options: GenerateBranchSummaryOptions): Promise<BranchSummaryResult>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Remote Compaction V2: streaming Responses compaction.
3
+ *
4
+ * Mirrors Codex `core/src/compact_remote_v2.rs`: append a `compaction_trigger`
5
+ * input item to the normal Responses stream, require exactly one streamed
6
+ * compaction output item, then install retained real user messages plus that
7
+ * compaction item as replacement history.
8
+ */
9
+ import type { CodexCompactionContext, FetchImpl, Model, ProviderSessionState } from "@linxiraos/pi-ai";
10
+ /** Retained-message budget Codex uses after streamed V2 compaction. */
11
+ export declare const V2_RETAINED_MESSAGE_TOKEN_BUDGET = 64000;
12
+ /** Max retries for V2 streaming compaction on transient stream errors. */
13
+ export declare const V2_COMPACTION_MAX_RETRIES = 2;
14
+ /** Timeout for V2 streaming compaction (3 minutes, same as V1). */
15
+ export declare const V2_COMPACTION_TIMEOUT_MS = 180000;
16
+ /** Token usage reported by the streamed V2 Responses completion. */
17
+ export interface CompactionV2Usage {
18
+ inputTokens: number;
19
+ outputTokens: number;
20
+ totalTokens: number;
21
+ cachedInputTokens?: number;
22
+ reasoningOutputTokens?: number;
23
+ }
24
+ /** Request body fields needed for Responses-stream V2 compaction. */
25
+ export interface CompactionV2Request {
26
+ model: string;
27
+ input: unknown[];
28
+ instructions: string;
29
+ retainedMessageBudget: number;
30
+ tools?: unknown[];
31
+ /** Responses reasoning param (effort + summary), matching a normal turn; omitted for non-reasoning models. */
32
+ reasoning?: {
33
+ effort: string;
34
+ summary: string;
35
+ };
36
+ sessionId?: string;
37
+ promptCacheKey?: string;
38
+ }
39
+ /** Response collected from the V2 stream and converted into replacement history. */
40
+ export interface CompactionV2Response {
41
+ compactionItem: Record<string, unknown>;
42
+ replacementHistory: Array<Record<string, unknown>>;
43
+ usedTokens: number;
44
+ usage?: CompactionV2Usage;
45
+ retainedImageCount: number;
46
+ }
47
+ /** Resolve the streaming Responses endpoint for a V2-capable model. */
48
+ export declare function getCompactionV2Endpoint(model: Model): string | undefined;
49
+ /** Check whether a model can use streaming V2 compaction. */
50
+ export declare function shouldUseCompactionV2Streaming(model: Model): model is Model<"openai-responses" | "azure-openai-responses" | "openai-codex-responses">;
51
+ /** Clamp the retained-message budget to Codex's known-safe 64K ceiling. */
52
+ export declare function resolveCompactionV2RetainedMessageBudget(value: number | undefined): number;
53
+ /** Build a V2 streaming compaction request from Responses-native history. */
54
+ export declare function buildCompactionV2Request(model: Model, input: unknown[], instructions: string, options?: {
55
+ tools?: unknown[];
56
+ reasoning?: {
57
+ effort: string;
58
+ summary: string;
59
+ };
60
+ sessionId?: string;
61
+ promptCacheKey?: string;
62
+ retainedMessageBudget?: number;
63
+ }): CompactionV2Request;
64
+ /** Request V2 compaction over the normal OpenAI Responses streaming endpoint. */
65
+ export declare function requestCompactionV2Streaming(model: Model, apiKey: string, request: CompactionV2Request, signal?: AbortSignal, options?: {
66
+ fetch?: FetchImpl;
67
+ timeoutMs?: number;
68
+ retryWait?: (delayMs: number, signal?: AbortSignal) => Promise<void>;
69
+ providerSessionState?: Map<string, ProviderSessionState>;
70
+ codexCompaction?: CodexCompactionContext;
71
+ preferWebsockets?: boolean;
72
+ }): Promise<CompactionV2Response>;
73
+ /** Build Codex-style V2 replacement history from prompt input plus compaction output. */
74
+ export declare function buildCompactionV2ReplacementHistory(input: unknown[], compactionItem: Record<string, unknown>, retainedMessageBudget?: number): {
75
+ replacementHistory: Array<Record<string, unknown>>;
76
+ retainedImageCount: number;
77
+ };
78
+ /** Store V2 replacement history in the OpenAI remote-compaction preserve slot. */
79
+ export declare function storeCompactionV2PreserveData(response: CompactionV2Response, model: Model): Record<string, unknown>;
80
+ /** Retrieve preserved OpenAI replacement history that V2 can extend. */
81
+ export declare function getCompactionV2PreserveData(preserveData: Record<string, unknown> | undefined): {
82
+ provider: string;
83
+ replacementHistory: Array<Record<string, unknown>>;
84
+ usedTokens: number;
85
+ } | undefined;
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Context compaction for long sessions.
3
+ *
4
+ * Pure functions for compaction logic. The session manager handles I/O,
5
+ * and after compaction the session is reloaded.
6
+ */
7
+ import { type Api, type ApiKey, type AssistantMessage, type CodexCompactionContext, type Context, type FetchImpl, type MessageAttribution, type Model, type OneshotRetryOptions, type ProviderSessionState, type SimpleStreamOptions, type Tool, type Usage } from "@linxiraos/pi-ai";
8
+ import { type AgentTelemetry } from "../telemetry.js";
9
+ import { ThinkingLevel } from "../thinking.js";
10
+ import type { AgentMessage } from "../types.js";
11
+ import type { SessionEntry } from "./entries.js";
12
+ import { type ConvertToLlm } from "./messages.js";
13
+ import { type FileOperations } from "./utils.js";
14
+ /** Details stored in CompactionEntry.details for file tracking */
15
+ export interface CompactionDetails {
16
+ readFiles: string[];
17
+ modifiedFiles: string[];
18
+ }
19
+ /** Result from compact() - SessionManager adds uuid/parentUuid when saving */
20
+ export interface CompactionResult<T = unknown> {
21
+ summary: string;
22
+ /** Short PR-style summary for display purposes. */
23
+ shortSummary?: string;
24
+ firstKeptEntryId: string;
25
+ tokensBefore: number;
26
+ /** Hook-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
27
+ details?: T;
28
+ /** Hook-provided data to persist alongside compaction entry. */
29
+ preserveData?: Record<string, unknown>;
30
+ }
31
+ export interface CompactionSettings {
32
+ enabled: boolean;
33
+ strategy?: "context-full" | "handoff" | "shake" | "snapcompact" | "off";
34
+ thresholdPercent?: number;
35
+ thresholdTokens?: number;
36
+ midTurnEnabled?: boolean;
37
+ /**
38
+ * Tokens reserved below the context window for the next prompt + response.
39
+ *
40
+ * Leave unset to use {@link DEFAULT_RESERVE_TOKENS}; the unset state is the
41
+ * provenance signal that lets small-window recovery replace the default with
42
+ * a proportional reserve (see {@link resolveBudgetReserveTokens}). An
43
+ * explicit value — even one equal to the default — is always honored.
44
+ */
45
+ reserveTokens?: number;
46
+ keepRecentTokens: number;
47
+ autoContinue?: boolean;
48
+ remoteEnabled?: boolean;
49
+ remoteEndpoint?: string;
50
+ remoteStreamingV2Enabled?: boolean;
51
+ v2RetainedMessageBudget?: number;
52
+ }
53
+ /** Reserve applied when {@link CompactionSettings.reserveTokens} is unset. */
54
+ export declare const DEFAULT_RESERVE_TOKENS = 16384;
55
+ /**
56
+ * Hard ceiling on a generated compaction summary.
57
+ *
58
+ * The summary budget is `floor(0.8 * reserveTokens)`, and the effective reserve is
59
+ * at least 15% of the declared context window, so a 1M-token window authorizes a
60
+ * ~120k-token summary. At that size the model copies rather than compresses, and
61
+ * output is the slowest and most expensive token class. Capping absolutely keeps
62
+ * the compression ratio improving with window size instead of degrading. The value
63
+ * mirrors {@link DEFAULT_RESERVE_TOKENS} so this adds no new tuning constant.
64
+ */
65
+ export declare const MAX_SUMMARY_TOKENS = 16384;
66
+ export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
67
+ /** Whether a compaction candidate preserves provider-native transport under the effective settings. */
68
+ export declare function shouldUseProviderNativeCompaction(model: Model, settings: Pick<CompactionSettings, "remoteEnabled" | "remoteStreamingV2Enabled">): boolean;
69
+ /**
70
+ * Calculate total context tokens from usage.
71
+ * Prefers an explicit provider-reported context occupancy when available.
72
+ * Otherwise uses totalTokens and falls back to computing from billable
73
+ * components. Provider-side orchestration tokens are billable but never replay
74
+ * into the conversation prefix, so they are excluded from context sizing.
75
+ */
76
+ export declare function calculateContextTokens(usage: Usage): number;
77
+ export declare function calculatePromptTokens(usage: Usage): number;
78
+ export declare function hasContextTokenUsage(usage: Usage): boolean;
79
+ /**
80
+ * Find the last non-aborted assistant message usage from session entries.
81
+ */
82
+ export declare function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefined;
83
+ /**
84
+ * Effective reserve: at least 15% of context window or the configured floor
85
+ * (defaulting to {@link DEFAULT_RESERVE_TOKENS} when unset), whichever is larger.
86
+ */
87
+ export declare function effectiveReserveTokens(contextWindow: number, settings: CompactionSettings): number;
88
+ /**
89
+ * Reserve used when deciding whether a prompt still fits inside the model window.
90
+ *
91
+ * The default absolute reserve predates small bundled windows and can leave no
92
+ * practical budget there; recover a DEFAULTED reserve that is impossible for
93
+ * the window with the 15% proportional reserve (clamped to >= 1 so the derived
94
+ * threshold stays strictly below the window even for tiny test windows).
95
+ * Explicit valid reserves — including one that happens to equal the default —
96
+ * still win, because they intentionally shrink the usable prompt budget;
97
+ * provenance is carried by `settings.reserveTokens` being unset, never by
98
+ * comparing values against the default.
99
+ */
100
+ export declare function resolveBudgetReserveTokens(contextWindow: number, settings: CompactionSettings): number;
101
+ /**
102
+ * Check if compaction should trigger based on context usage.
103
+ */
104
+ export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean;
105
+ /**
106
+ * Context tokens to feed the compaction decision, floored by a local estimate of
107
+ * the stored conversation.
108
+ *
109
+ * The provider-reported usage is normally ground truth, but a
110
+ * `before_provider_request` payload transform — a compression extension (e.g.
111
+ * Headroom), an obfuscator, or inline snapcompact — can shrink the request below
112
+ * the real stored conversation. The provider then reports deflated prompt
113
+ * tokens, so anchoring compaction purely on that usage lets the real history
114
+ * grow unbounded until it overflows and native compaction can no longer run.
115
+ * Flooring by the agent's own estimate of the stored conversation keeps the
116
+ * compaction trigger honest regardless of on-wire compression. (Display/cost
117
+ * accounting still uses the exact provider usage; only the compaction decision
118
+ * takes the floor.)
119
+ */
120
+ export declare function compactionContextTokens(providerContextTokens: number, storedConversationEstimate: number): number;
121
+ export declare function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings): number;
122
+ /**
123
+ * Estimate token count for a message using cl100k_base via the native
124
+ * tokenizer. This is not Claude's first-party tokenizer (Anthropic doesn't
125
+ * publish one) but is within ~5–10% across English/code text.
126
+ *
127
+ * `excludeEncryptedReasoning` drops opaque provider reasoning payloads
128
+ * (`thinkingSignature`, `redactedThinking`) from the estimate. Those are billed
129
+ * by the provider on replay, so the default counts them — but their *local*
130
+ * byte size can diverge wildly from what the provider charges, so the
131
+ * compaction floor (which only needs the reliably-countable, on-wire-compressible
132
+ * content) excludes them to avoid false triggers on thinking-heavy turns.
133
+ */
134
+ export declare function estimateTokens(message: AgentMessage, options?: {
135
+ excludeEncryptedReasoning?: boolean;
136
+ }): number;
137
+ /**
138
+ * Find the user message (or bashExecution) that starts the turn containing the given entry index.
139
+ * Returns -1 if no turn start found before the index.
140
+ * BashExecutionMessage is treated like a user message for turn boundaries.
141
+ */
142
+ export declare function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number;
143
+ export interface CutPointResult {
144
+ /** Index of first entry to keep */
145
+ firstKeptEntryIndex: number;
146
+ /** Index of user message that starts the turn being split, or -1 if not splitting */
147
+ turnStartIndex: number;
148
+ /** Whether this cut splits a turn (cut point is not a user message) */
149
+ isSplitTurn: boolean;
150
+ }
151
+ /**
152
+ * Find the cut point in session entries that keeps approximately `keepRecentTokens`.
153
+ *
154
+ * Algorithm: Walk backwards from newest, accumulating estimated message sizes.
155
+ * Stop when we've accumulated >= keepRecentTokens. Cut at that point.
156
+ *
157
+ * Can cut at user OR assistant messages (never tool results). When cutting at an
158
+ * assistant message with tool calls, its tool results come after and will be kept.
159
+ *
160
+ * Returns CutPointResult with:
161
+ * - firstKeptEntryIndex: the entry index to start keeping from
162
+ * - turnStartIndex: if cutting mid-turn, the user message that started that turn
163
+ * - isSplitTurn: whether we're cutting in the middle of a turn
164
+ *
165
+ * Only considers entries between `startIndex` and `endIndex` (exclusive).
166
+ */
167
+ export declare function findCutPoint(entries: SessionEntry[], startIndex: number, endIndex: number, keepRecentTokens: number): CutPointResult;
168
+ export declare const AUTO_HANDOFF_THRESHOLD_FOCUS: string;
169
+ /**
170
+ * Generate a summary of the conversation using the LLM.
171
+ * If previousSummary is provided, uses the update prompt to merge.
172
+ */
173
+ export interface SummaryOptions {
174
+ promptOverride?: string;
175
+ extraContext?: string[];
176
+ remoteEndpoint?: string;
177
+ remoteInstructions?: string;
178
+ initiatorOverride?: MessageAttribution;
179
+ metadata?: Record<string, unknown>;
180
+ convertToLlm?: ConvertToLlm;
181
+ /**
182
+ * Optional telemetry handle. When provided, every LLM call emitted during
183
+ * compaction is wrapped in an OTEL chat span tagged with
184
+ * `pi.gen_ai.oneshot.kind` (`compaction_summary`, `compaction_short_summary`,
185
+ * or `compaction_turn_prefix`). `undefined` keeps the call paths zero-cost.
186
+ */
187
+ telemetry?: AgentTelemetry;
188
+ /**
189
+ * Active session thinking level. Threaded from `agent-session.ts` so
190
+ * compaction honors the user's `/model` thinking selection instead of
191
+ * silently overriding it with `Effort.High` (the historical default).
192
+ * `undefined` / `ThinkingLevel.Inherit` falls back to that historical
193
+ * default; `ThinkingLevel.Off` omits reasoning entirely. See
194
+ * `resolveCompactionEffort` for the conversion contract.
195
+ */
196
+ thinkingLevel?: ThinkingLevel;
197
+ /** Session routing key for remote compaction transports with sticky provider sessions. */
198
+ sessionId?: string;
199
+ /** Prompt-cache key for remote compaction transports that support provider prefix caching. */
200
+ promptCacheKey?: string;
201
+ /** Mutable provider state used to keep Codex compaction on the live session identity. */
202
+ providerSessionState?: Map<string, ProviderSessionState>;
203
+ /** Whether Codex remote compaction should prefer the provider WebSocket transport. */
204
+ preferWebsockets?: boolean;
205
+ /** Classification shared by every provider request in this logical compaction. */
206
+ codexCompaction?: CodexCompactionContext;
207
+ /** Provider-visible tools for remote compaction transports that replay native tool history. */
208
+ tools?: Tool[];
209
+ /** Optional fetch implementation threaded into remote compaction calls. */
210
+ fetch?: FetchImpl;
211
+ /**
212
+ * Optional completion transport override for host-level request wrappers
213
+ * (e.g. the coding-agent provider-concurrency limiter). When provided,
214
+ * every local summarization oneshot (`generateSummary`,
215
+ * `generateTurnPrefixSummary`, `generateShortSummary`) routes through it
216
+ * instead of the default `completeSimple`, so cap policies enforced on
217
+ * the live agent turn also bracket compaction HTTP requests.
218
+ */
219
+ completeImpl?: <TApi extends Api>(model: Model<TApi>, ctx: Context, options: SimpleStreamOptions) => Promise<AssistantMessage>;
220
+ /**
221
+ * Transient-failure retry for the summarization oneshots (`generateSummary`,
222
+ * `generateShortSummary`, `generateTurnPrefixSummary`).
223
+ *
224
+ * Defaults to enabled, which is what a one-shot caller such as manual
225
+ * `/compact` needs: a single Anthropic `overloaded_error` / 429 / 529 should
226
+ * not abort compaction and leave the context full.
227
+ *
228
+ * Pass `false` when the CALLER already owns a retry loop around the whole
229
+ * compaction attempt — auto-compaction does — otherwise the two budgets
230
+ * multiply (10 outer attempts x 3 inner = 30 requests) and each outer wait
231
+ * stacks on top of the inner backoff.
232
+ */
233
+ oneshotRetry?: OneshotRetryOptions | false;
234
+ }
235
+ export declare function generateSummary(currentMessages: AgentMessage[], model: Model, reserveTokens: number, apiKey: ApiKey, signal?: AbortSignal, customInstructions?: string, previousSummary?: string, options?: SummaryOptions): Promise<string>;
236
+ export interface HandoffOptions {
237
+ /** Live agent system prompt — passed verbatim so providers hit the cached prefix. */
238
+ systemPrompt: string[];
239
+ /** Live agent tool list — same purpose. Forced to `toolChoice: "none"`. */
240
+ tools?: Tool[];
241
+ customInstructions?: string;
242
+ convertToLlm?: ConvertToLlm;
243
+ initiatorOverride?: MessageAttribution;
244
+ metadata?: Record<string, unknown>;
245
+ /**
246
+ * Optional telemetry handle. When provided, the handoff LLM call is
247
+ * wrapped in an OTEL chat span tagged with `pi.gen_ai.oneshot.kind = "handoff"`.
248
+ */
249
+ telemetry?: AgentTelemetry;
250
+ /**
251
+ * Active session thinking level. Threaded from `agent-session.ts` so
252
+ * handoff generation honors the user's `/model` thinking selection
253
+ * instead of silently overriding it with `Effort.High`. See
254
+ * `resolveCompactionEffort` for the conversion contract.
255
+ */
256
+ thinkingLevel?: ThinkingLevel;
257
+ }
258
+ export declare function renderHandoffPrompt(customInstructions?: string): string;
259
+ export interface HandoffFromContextOptions {
260
+ /**
261
+ * Stream options mirrored from the live agent turn: `apiKey`, `signal`, the
262
+ * `sessionId`/`promptCacheKey` cache-routing pair, `serviceTier`, and the
263
+ * session's payload/response hooks. Sending the same routing + payload shape
264
+ * the main loop uses is what lets the handoff oneshot READ the provider
265
+ * prompt cache the live turn populated instead of cold-missing the whole
266
+ * prefix. `reasoning` and `toolChoice` are set internally and override
267
+ * anything provided here.
268
+ */
269
+ streamOptions: SimpleStreamOptions;
270
+ /** Optional completion transport override for host-level request wrappers. */
271
+ completeImpl?: <TApi extends Api>(model: Model<TApi>, ctx: Context, options: SimpleStreamOptions) => Promise<AssistantMessage>;
272
+ /** See {@link HandoffOptions.telemetry}. */
273
+ telemetry?: AgentTelemetry;
274
+ /** See {@link HandoffOptions.thinkingLevel}. */
275
+ thinkingLevel?: ThinkingLevel;
276
+ }
277
+ /**
278
+ * Run the handoff oneshot against a fully-built provider {@link Context}.
279
+ *
280
+ * The caller assembles `context` exactly like a live agent turn — same system
281
+ * prompt, normalized tools, transformed + obfuscated message history, with the
282
+ * trailing handoff-prompt message already appended — and supplies
283
+ * `streamOptions` that mirror the live turn's cache routing. That keeps the
284
+ * cache-preserving context construction in the host (which owns the transform
285
+ * pipeline) while this function centralizes the handoff request contract:
286
+ * cache-first `toolChoice: "none"`, clamped reasoning effort, one retry for
287
+ * auto-only `tool_choice` providers, oneshot telemetry, text-only extraction,
288
+ * and provider-error mapping.
289
+ */
290
+ export declare function generateHandoffFromContext(context: Context, model: Model, options: HandoffFromContextOptions): Promise<string>;
291
+ export declare function generateHandoff(messages: AgentMessage[], model: Model, apiKey: ApiKey, options: HandoffOptions, signal?: AbortSignal): Promise<string>;
292
+ export interface CompactionPreparation {
293
+ /** UUID of first entry to keep */
294
+ firstKeptEntryId: string;
295
+ /** Messages that will be summarized and discarded */
296
+ messagesToSummarize: AgentMessage[];
297
+ /** Messages that will be turned into turn prefix summary (if splitting) */
298
+ turnPrefixMessages: AgentMessage[];
299
+ /** Messages kept in full after compaction (recent history) */
300
+ recentMessages: AgentMessage[];
301
+ /** Whether this is a split turn (cut point in middle of turn) */
302
+ isSplitTurn: boolean;
303
+ tokensBefore: number;
304
+ /** Summary from previous compaction, for iterative update */
305
+ previousSummary?: string;
306
+ /** Preserved opaque compaction payload from the previous compaction, if any. */
307
+ previousPreserveData?: Record<string, unknown>;
308
+ /** File operations extracted from messagesToSummarize */
309
+ fileOps: FileOperations;
310
+ /** Compaction settions from settings.jsonl */
311
+ settings: CompactionSettings;
312
+ }
313
+ export declare function prepareCompaction(pathEntries: SessionEntry[], settings: CompactionSettings, activeModel?: Model): CompactionPreparation | undefined;
314
+ /**
315
+ * Generate summaries for compaction using prepared data.
316
+ * Returns CompactionResult - SessionManager adds id/parentId when saving.
317
+ *
318
+ * @param preparation - Pre-calculated preparation from prepareCompaction()
319
+ * @param customInstructions - Optional custom focus for the summary
320
+ */
321
+ export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: ApiKey, customInstructions?: string, signal?: AbortSignal, options?: SummaryOptions): Promise<CompactionResult>;