@linxiraos/pi-agent-core 1.0.0

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 (71) hide show
  1. package/CHANGELOG.md +1263 -0
  2. package/README.md +473 -0
  3. package/dist/types/agent-loop.d.ts +137 -0
  4. package/dist/types/agent.d.ts +465 -0
  5. package/dist/types/append-only-context.d.ts +131 -0
  6. package/dist/types/compaction/branch-summarization.d.ts +101 -0
  7. package/dist/types/compaction/compaction-v2-streaming.d.ts +85 -0
  8. package/dist/types/compaction/compaction.d.ts +307 -0
  9. package/dist/types/compaction/entries.d.ts +111 -0
  10. package/dist/types/compaction/errors.d.ts +38 -0
  11. package/dist/types/compaction/index.d.ts +13 -0
  12. package/dist/types/compaction/message-cache.d.ts +23 -0
  13. package/dist/types/compaction/messages.d.ts +79 -0
  14. package/dist/types/compaction/openai.d.ts +117 -0
  15. package/dist/types/compaction/pruning.d.ts +105 -0
  16. package/dist/types/compaction/shake.d.ts +97 -0
  17. package/dist/types/compaction/tool-protection.d.ts +19 -0
  18. package/dist/types/compaction/utils.d.ts +61 -0
  19. package/dist/types/compaction.d.ts +1 -0
  20. package/dist/types/index.d.ts +13 -0
  21. package/dist/types/pause.d.ts +46 -0
  22. package/dist/types/proxy.d.ts +91 -0
  23. package/dist/types/replay-policy.d.ts +5 -0
  24. package/dist/types/run-collector.d.ts +196 -0
  25. package/dist/types/telemetry.d.ts +608 -0
  26. package/dist/types/thinking.d.ts +18 -0
  27. package/dist/types/tokenizer.d.ts +2 -0
  28. package/dist/types/types.d.ts +781 -0
  29. package/dist/types/utils/yield.d.ts +71 -0
  30. package/package.json +79 -0
  31. package/src/agent-loop.ts +2926 -0
  32. package/src/agent.ts +1748 -0
  33. package/src/append-only-context.ts +348 -0
  34. package/src/compaction/branch-summarization.ts +369 -0
  35. package/src/compaction/compaction-v2-streaming.ts +846 -0
  36. package/src/compaction/compaction.ts +1733 -0
  37. package/src/compaction/entries.ts +141 -0
  38. package/src/compaction/errors.ts +47 -0
  39. package/src/compaction/index.ts +14 -0
  40. package/src/compaction/message-cache.ts +92 -0
  41. package/src/compaction/messages.ts +241 -0
  42. package/src/compaction/openai.ts +992 -0
  43. package/src/compaction/prompts/auto-handoff-threshold-focus.md +1 -0
  44. package/src/compaction/prompts/branch-summary-context.md +5 -0
  45. package/src/compaction/prompts/branch-summary-preamble.md +2 -0
  46. package/src/compaction/prompts/branch-summary.md +30 -0
  47. package/src/compaction/prompts/compaction-short-summary.md +9 -0
  48. package/src/compaction/prompts/compaction-summary-context.md +5 -0
  49. package/src/compaction/prompts/compaction-summary.md +38 -0
  50. package/src/compaction/prompts/compaction-turn-prefix.md +17 -0
  51. package/src/compaction/prompts/compaction-update-summary.md +45 -0
  52. package/src/compaction/prompts/context-window-truncated-output.md +1 -0
  53. package/src/compaction/prompts/file-operations.md +5 -0
  54. package/src/compaction/prompts/handoff-document.md +49 -0
  55. package/src/compaction/prompts/snapcompact-archive-context.md +3 -0
  56. package/src/compaction/prompts/summarization-system.md +3 -0
  57. package/src/compaction/pruning.ts +427 -0
  58. package/src/compaction/shake.ts +446 -0
  59. package/src/compaction/tool-protection.ts +65 -0
  60. package/src/compaction/utils.ts +343 -0
  61. package/src/compaction.ts +1 -0
  62. package/src/index.ts +26 -0
  63. package/src/pause.ts +107 -0
  64. package/src/proxy.ts +391 -0
  65. package/src/replay-policy.ts +13 -0
  66. package/src/run-collector.ts +631 -0
  67. package/src/telemetry.ts +2078 -0
  68. package/src/thinking.ts +20 -0
  69. package/src/tokenizer.ts +27 -0
  70. package/src/types.ts +875 -0
  71. package/src/utils/yield.ts +183 -0
@@ -0,0 +1,23 @@
1
+ import type { AgentMessage } from "../types.js";
2
+ /**
3
+ * Register a cache tied to message identity so owner mutations in this package
4
+ * (prune/shake) can invalidate it across the package boundary. Returns an
5
+ * unregister function. The coding-agent `convertToLlm` memo registers here.
6
+ */
7
+ export declare function registerMessageCacheInvalidator(invalidate: (message: AgentMessage) => void): () => void;
8
+ /**
9
+ * True when this message's estimate is safe to cache by identity. Non-assistants
10
+ * are immutable once appended; assistants are cached only once settled (see the
11
+ * settle-gate invariant above).
12
+ */
13
+ export declare function isEstimateCacheable(message: AgentMessage): boolean;
14
+ /** Read a cached estimate for the given option split, or `undefined` on miss. */
15
+ export declare function readEstimateCache(message: AgentMessage, excludeEncryptedReasoning: boolean): number | undefined;
16
+ /** Store an estimate for the given option split. */
17
+ export declare function writeEstimateCache(message: AgentMessage, excludeEncryptedReasoning: boolean, value: number): void;
18
+ /**
19
+ * Drop every cached derivation of `message` after an in-place rewrite. Owners of
20
+ * mutation (prune, shake, strip-images) call this at the mutation seam so the
21
+ * next convert/estimate pass recomputes from the new content.
22
+ */
23
+ export declare function invalidateMessageCache(message: AgentMessage): void;
@@ -0,0 +1,79 @@
1
+ import type { ImageContent, Message, MessageAttribution, ProviderPayload, TextContent } from "@linxiraos/pi-ai";
2
+ import type { AgentMessage } from "../types.js";
3
+ export interface CustomMessage<T = unknown> {
4
+ role: "custom";
5
+ customType: string;
6
+ content: string | (TextContent | ImageContent)[];
7
+ display: boolean;
8
+ details?: T;
9
+ /** Who initiated this message for billing/attribution semantics. */
10
+ attribution?: MessageAttribution;
11
+ timestamp: number;
12
+ }
13
+ /** Legacy hook message type (pre-extensions). Kept for session migration. */
14
+ export interface HookMessage<T = unknown> {
15
+ role: "hookMessage";
16
+ customType: string;
17
+ content: string | (TextContent | ImageContent)[];
18
+ display: boolean;
19
+ details?: T;
20
+ /** Who initiated this message for billing/attribution semantics. */
21
+ attribution?: MessageAttribution;
22
+ timestamp: number;
23
+ }
24
+ export interface BranchSummaryMessage {
25
+ role: "branchSummary";
26
+ summary: string;
27
+ fromId: string;
28
+ timestamp: number;
29
+ }
30
+ export interface CompactionSummaryMessage {
31
+ role: "compactionSummary";
32
+ summary: string;
33
+ shortSummary?: string;
34
+ tokensBefore: number;
35
+ providerPayload?: ProviderPayload;
36
+ /** Runtime-only ordered archive blocks for snapcompact: old text region,
37
+ * imaged middle, then new text region. When present, `summary` is already
38
+ * the final lead-in text (no legacy wrapper applied). */
39
+ blocks?: (TextContent | ImageContent)[];
40
+ /** Snapcompact image blocks, kept for display counts / legacy consumers. */
41
+ images?: ImageContent[];
42
+ /** Post-pass dead-end warning attached to this compaction (progress guard). */
43
+ warning?: string;
44
+ timestamp: number;
45
+ }
46
+ export type CoreCompactionMessage = CustomMessage | HookMessage | BranchSummaryMessage | CompactionSummaryMessage;
47
+ declare module "../types.js" {
48
+ interface CustomAgentMessages {
49
+ custom: CustomMessage;
50
+ hookMessage: HookMessage;
51
+ branchSummary: BranchSummaryMessage;
52
+ compactionSummary: CompactionSummaryMessage;
53
+ }
54
+ }
55
+ export type ConvertToLlm = (messages: AgentMessage[]) => Message[];
56
+ export declare function renderBranchSummaryContext(summary: string): string;
57
+ export declare function renderCompactionSummaryContext(summary: string): string;
58
+ export declare function createBranchSummaryMessage(summary: string, fromId: string, timestamp: string): BranchSummaryMessage;
59
+ export declare function createCompactionSummaryMessage(summary: string, tokensBefore: number, timestamp: string, shortSummary?: string, providerPayload?: ProviderPayload, images?: ImageContent[], blocks?: (TextContent | ImageContent)[], warning?: string): CompactionSummaryMessage;
60
+ export declare function createCustomMessage(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details: unknown | undefined, timestamp: string, attribution?: MessageAttribution): CustomMessage;
61
+ /**
62
+ * Transform a single core-domain agent message to its LLM form; `undefined`
63
+ * drops it from the provider request.
64
+ *
65
+ * Single source of truth for the core roles (user/developer/assistant/
66
+ * toolResult) and the compaction messages owned by this package. Embedders
67
+ * with their own app messages (e.g. the coding agent) handle their custom
68
+ * roles and delegate every core role here — duplicating these cases is how
69
+ * snapcompact frames once silently fell off the provider request.
70
+ */
71
+ export declare function convertMessageToLlm(message: AgentMessage): Message | undefined;
72
+ /**
73
+ * Default compaction-domain transformer.
74
+ *
75
+ * Embedders with their own app messages should pass a richer transformer through
76
+ * `SummaryOptions.convertToLlm`; this default intentionally preserves only the
77
+ * core LLM roles and the compaction messages owned by this package.
78
+ */
79
+ export declare function defaultConvertToLlm(messages: AgentMessage[]): Message[];
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Remote compaction utilities.
3
+ *
4
+ * Provider-side conversation summarization endpoints. Three flavors:
5
+ *
6
+ * - **OpenAI remote compaction V2** (Responses streaming): appends a
7
+ * `compaction_trigger` input item to the normal stream and stores the returned
8
+ * `compaction` item with retained real user messages in `preserveData`.
9
+ * - **OpenAI remote compaction V1** (`/responses/compact`): preserves encrypted
10
+ * reasoning across compactions by submitting the full responses-API native
11
+ * history and storing the returned `compaction` / `compaction_summary`
12
+ * item in `preserveData` so future turns can replay the encrypted state.
13
+ * - **Generic remote compaction**: a thin POST helper for self-hosted
14
+ * summarization endpoints that accept `{ systemPrompt, prompt }` and reply
15
+ * with `{ summary, shortSummary? }`.
16
+ */
17
+ import type { CodexCompactionContext, FetchImpl, Message, Model, ProviderSessionState } from "@linxiraos/pi-ai/types";
18
+ export * from "./compaction-v2-streaming.js";
19
+ export declare const OPENAI_REMOTE_COMPACTION_PRESERVE_KEY = "openaiRemoteCompaction";
20
+ /**
21
+ * Hard ceiling on remote compaction HTTP requests. Unlike every provider
22
+ * stream (guarded by first-event/idle watchdogs in pi-ai), these are raw
23
+ * fetches awaiting one non-streamed JSON body — a connection silently dropped
24
+ * by a middlebox would otherwise hang the whole compaction pipeline forever
25
+ * (frozen "Auto context-full maintenance…" spinner, manual /compact queueing
26
+ * behind it). On timeout the caller falls back to local summarization.
27
+ */
28
+ export declare const REMOTE_COMPACTION_TIMEOUT_MS = 180000;
29
+ export declare const CONTEXT_WINDOW_TRUNCATED_OUTPUT_MESSAGE: string;
30
+ export interface TrimRemoteCompactionInputResult {
31
+ input: Array<Record<string, unknown>>;
32
+ rewrittenOutputs: number;
33
+ estimatedTokensBefore: number;
34
+ estimatedTokensAfter: number;
35
+ }
36
+ /**
37
+ * Preserve the full native transcript unless trailing tool outputs alone push a
38
+ * remote compaction request beyond the model window. Replacing only those
39
+ * outputs keeps call/result pairing and all earlier assistant/reasoning history,
40
+ * matching Codex's recovery path for oversized tool turns.
41
+ */
42
+ export declare function trimRemoteCompactionInputToContextWindow(input: Array<Record<string, unknown>>, contextWindow: number | null | undefined, instructions: string, tools?: unknown[]): TrimRemoteCompactionInputResult;
43
+ export type OpenAiRemoteCompactionItem = {
44
+ type: "compaction" | "compaction_summary";
45
+ encrypted_content?: string;
46
+ summary?: string;
47
+ };
48
+ export interface OpenAiRemoteCompactionPreserveData {
49
+ provider?: string;
50
+ replacementHistory: Array<Record<string, unknown>>;
51
+ compactionItem: OpenAiRemoteCompactionItem;
52
+ }
53
+ export interface OpenAiRemoteCompactionRequest {
54
+ model: string;
55
+ input: Array<Record<string, unknown>>;
56
+ instructions: string;
57
+ reasoning?: {
58
+ context?: string;
59
+ [key: string]: unknown;
60
+ };
61
+ include?: string[];
62
+ }
63
+ export interface OpenAiRemoteCompactionResponse extends OpenAiRemoteCompactionPreserveData {
64
+ }
65
+ export interface RemoteCompactionRequest {
66
+ systemPrompt: string;
67
+ prompt: string;
68
+ maxTokens?: number;
69
+ }
70
+ export interface RemoteCompactionResponse {
71
+ summary: string;
72
+ shortSummary?: string;
73
+ }
74
+ export declare function shouldUseOpenAiRemoteCompaction(model: Model): boolean;
75
+ export declare function getPreservedOpenAiRemoteCompactionData(preserveData: Record<string, unknown> | undefined): OpenAiRemoteCompactionPreserveData | undefined;
76
+ export declare function withOpenAiRemoteCompactionPreserveData(preserveData: Record<string, unknown> | undefined, remoteCompaction: OpenAiRemoteCompactionPreserveData | undefined): Record<string, unknown> | undefined;
77
+ /**
78
+ * Build the OpenAI Responses-API native history array from LLM messages.
79
+ *
80
+ * Caller is responsible for converting any custom message types to
81
+ * `Message[]` first (e.g. via the agent's `convertToLlm`); this function
82
+ * operates purely on the LLM-domain shape.
83
+ *
84
+ * @param messages - LLM messages to encode.
85
+ * @param model - Target model (used for provider gating + tool-call id rules).
86
+ * @param previousReplacementHistory - History from a prior compaction whose
87
+ * encrypted reasoning we want to preserve.
88
+ */
89
+ export declare function buildOpenAiNativeHistory(messages: Message[], model: Model, previousReplacementHistory?: Array<Record<string, unknown>>): Array<Record<string, unknown>>;
90
+ export declare function requestOpenAiRemoteCompaction(model: Model, apiKey: string, compactInput: Array<Record<string, unknown>>, instructions: string, signal?: AbortSignal, opts?: {
91
+ fetch?: FetchImpl;
92
+ timeoutMs?: number;
93
+ sessionId?: string;
94
+ providerSessionState?: Map<string, ProviderSessionState>;
95
+ codexCompaction?: CodexCompactionContext;
96
+ }): Promise<OpenAiRemoteCompactionResponse>;
97
+ /**
98
+ * Generic remote-compaction POST. Two wire shapes are auto-selected by
99
+ * endpoint suffix so a single `compaction.remoteEndpoint` setting can point at
100
+ * either a purpose-built omp summarizer (`{systemPrompt, prompt}` → `{summary}`)
101
+ * or any OpenAI-compatible chat-completions server (`/chat/completions`,
102
+ * `/v1/chat/completions`, …) as reported for llama.cpp / vLLM / etc. in
103
+ * issue #4630: without this, the omp payload was rejected with
104
+ * HTTP 400 `"'messages' is required"`, compaction silently fell back to
105
+ * local summarization, and context grew unbounded.
106
+ *
107
+ * When `context.model` is provided the chat-completions body is tagged with
108
+ * that model's wire id (llama.cpp requires the field) and `context.apiKey` is
109
+ * forwarded as `Authorization: Bearer`. Callers wrap this in `withAuth` so
110
+ * 401s force-refresh through the standard credential rotation policy.
111
+ */
112
+ export declare function requestRemoteCompaction(endpoint: string, request: RemoteCompactionRequest, signal?: AbortSignal, opts?: {
113
+ fetch?: FetchImpl;
114
+ timeoutMs?: number;
115
+ model?: Model;
116
+ apiKey?: string;
117
+ }): Promise<RemoteCompactionResponse>;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Tool output pruning utilities for compaction.
3
+ */
4
+ import type { SessionEntry } from "./entries.js";
5
+ import { type ProtectedToolMatcher } from "./tool-protection.js";
6
+ export interface PruneConfig {
7
+ /** Keep the most recent tool output tokens intact. */
8
+ protectTokens: number;
9
+ /** Only prune if total savings meets this threshold. */
10
+ minimumSavings: number;
11
+ /** Tool-result protection matchers. String entries protect every result from that tool; predicates may inspect the paired tool call. */
12
+ protectedTools: ProtectedToolMatcher[];
13
+ /**
14
+ * Optional supersede key function (see {@link SupersedePruneConfig.supersedeKey}).
15
+ * When provided, superseded tool results are pruned first — even inside the
16
+ * `protectTokens` window — before age-based victims. Absent, behavior is
17
+ * unchanged.
18
+ */
19
+ supersedeKey?: SupersedeKeyFn;
20
+ /** Useless-flagged results bypass the protect window (see {@link USELESS_NOTICE}). Default true. */
21
+ pruneUseless?: boolean;
22
+ /**
23
+ * Compaction boundary: the `firstKeptEntryId` of the latest compaction on
24
+ * the branch. Entries at indices BEFORE this id are summarized away and never
25
+ * sent to the model, so mutating them only churns persisted history without
26
+ * shrinking the prompt — they are skipped. Undefined = no compaction (the
27
+ * whole branch is sent).
28
+ */
29
+ keepBoundaryId?: string;
30
+ /**
31
+ * Prompt-cache guard. When set, a tool result whose all-message suffix
32
+ * (tokens of every message after it) EXCEEDS this is part of the warm,
33
+ * already-sent cache prefix: mutating it forces the provider to re-write the
34
+ * whole suffix (cacheWrite premium). Such results — including superseded and
35
+ * useless ones, which otherwise bypass {@link protectTokens} — are left for
36
+ * compaction/shake (which rebuild the cache anyway) to reclaim. Undefined =
37
+ * no cache guard (legacy: superseded/useless prune at any depth).
38
+ */
39
+ cacheWarmSuffixTokens?: number;
40
+ }
41
+ export declare const DEFAULT_PRUNE_CONFIG: PruneConfig;
42
+ export interface PruneResult {
43
+ prunedCount: number;
44
+ tokensSaved: number;
45
+ }
46
+ /** Exact placeholder written over a superseded tool result. */
47
+ export declare const SUPERSEDED_NOTICE = "[Superseded by a newer read of this file]";
48
+ /** Exact placeholder written over an elided useless tool result. */
49
+ export declare const USELESS_NOTICE = "[Uneventful result elided]";
50
+ /**
51
+ * Maps a tool call to a supersede key. Results sharing a key form a group in
52
+ * which every result except the newest is a supersede candidate. A key `K`
53
+ * additionally supersedes keys with prefix `K + "\u0000"` (selector-free read
54
+ * supersedes selector-carrying reads of the same base path). Return
55
+ * `undefined` to exempt a call from supersede grouping.
56
+ */
57
+ export type SupersedeKeyFn = (toolName: string, args: Record<string, unknown>) => string | undefined;
58
+ export interface SupersedePruneConfig {
59
+ /** Supersede key function; results sharing a key supersede older ones. */
60
+ supersedeKey?: SupersedeKeyFn;
61
+ /** Also prune results flagged useless by their tool. Default false. */
62
+ pruneUseless?: boolean;
63
+ /** Prune a candidate now when all messages after it total at most this many estimated tokens. Default 8 000. */
64
+ suffixTokenLimit?: number;
65
+ /**
66
+ * Prune all candidates when the last message is at least this old: the
67
+ * provider prompt cache is then cold, so re-writing it is free. MUST exceed
68
+ * the cache retention (Anthropic "long" = 1h) or a still-warm prefix is busted
69
+ * by the flush. Default 30 min — callers on long retention override it.
70
+ */
71
+ idleFlushMs?: number;
72
+ /** Clock override for tests. */
73
+ now?: number;
74
+ /**
75
+ * Compaction boundary (`firstKeptEntryId` of the latest compaction). Entries
76
+ * before it are summarized away and never sent, so they are skipped in every
77
+ * path — including the idle flush — to avoid pointless history churn.
78
+ * Undefined = no compaction (the whole branch is sent).
79
+ */
80
+ keepBoundaryId?: string;
81
+ /** Tool-result protection matchers (same contract as {@link PruneConfig.protectedTools}). */
82
+ protectedTools: ProtectedToolMatcher[];
83
+ }
84
+ /**
85
+ * Prune superseded tool results (e.g. stale `read` outputs replaced by a newer
86
+ * read of the same file) and, when `pruneUseless` is set, results their tool
87
+ * flagged contextually useless. Cheap, incremental, and prompt-cache-aware: a
88
+ * candidate is pruned now only when the suffix after it is small (tail case —
89
+ * the read→edit→read loop) or when the context has been idle long enough that
90
+ * the provider cache is cold anyway (then all still-sent candidates flush).
91
+ * Never mutates entries before `keepBoundaryId` (summarized away — not sent).
92
+ */
93
+ export declare function pruneSupersededToolResults(entries: SessionEntry[], config: SupersedePruneConfig): PruneResult;
94
+ export declare function pruneToolOutputs(entries: SessionEntry[], config?: PruneConfig): PruneResult;
95
+ /**
96
+ * Supersede key for the `read` tool: the file path with the trailing line/raw
97
+ * selector stripped (the read tool's own splitter grammar via
98
+ * {@link splitReadSelector}, e.g. `src/foo.ts:50-200`, `:2-4:raw`).
99
+ * Internal/URL-scheme paths (`skill://…`, `https://…`) are exempt.
100
+ * Selector-free reads key on the bare path; selector-carrying reads key on
101
+ * `path + "\u0000" + selector`, so two reads collide only when the newer is
102
+ * selector-free or the selectors are identical (the pass's prefix rule lets a
103
+ * bare-path read supersede selector-carrying reads of the same file).
104
+ */
105
+ export declare function readToolSupersedeKey(toolName: string, args: Record<string, unknown>): string | undefined;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Context-reducing surgical compaction ("shake").
3
+ *
4
+ * `shake` drops heavy content out of the live context mechanically: whole
5
+ * tool-call results and large fenced/XML blocks are replaced with short
6
+ * placeholders. This module is the pure layer — region detection and in-place
7
+ * mutation only. Artifact offload, persistence, and provider-session teardown
8
+ * are orchestrated by the caller (`AgentSession.shake`).
9
+ *
10
+ * Layering mirrors `pruning.ts`: no I/O here.
11
+ */
12
+ import type { CustomMessageEntry, SessionEntry, SessionMessageEntry } from "./entries.js";
13
+ import { type ProtectedToolMatcher } from "./tool-protection.js";
14
+ export interface ShakeConfig {
15
+ /** Keep the most recent context tokens (across all entries) intact. */
16
+ protectTokens: number;
17
+ /** Only shake when total estimated savings meets this threshold. */
18
+ minSavings: number;
19
+ /** Tool-result protection matchers. String entries protect every result from that tool; predicates may inspect the paired tool call. */
20
+ protectedTools: ProtectedToolMatcher[];
21
+ /** Minimum token size for a fenced/XML block to be eligible. */
22
+ fenceMinTokens: number;
23
+ /**
24
+ * Compaction boundary (`firstKeptEntryId` of the latest compaction). Entries
25
+ * before it are summarized away and never sent, so they are skipped — shaking
26
+ * them only churns persisted history. Undefined = no compaction (whole branch
27
+ * is sent). Note: shake still elides the warm cached prefix at/after the
28
+ * boundary — that is its job as a compaction-class reducer.
29
+ */
30
+ keepBoundaryId?: string;
31
+ }
32
+ /** Auto-shake config: protects the live tail, conservative thresholds. */
33
+ export declare const DEFAULT_SHAKE_CONFIG: ShakeConfig;
34
+ /**
35
+ * Manual `/shake`: aggressive — drops every eligible region across history,
36
+ * artifact recovery reads included (the user's full escape hatch).
37
+ */
38
+ export declare const AGGRESSIVE_SHAKE_CONFIG: ShakeConfig;
39
+ /** Compaction dead-end rescue: aggressive reach, but artifact recovery reads stay protected. */
40
+ export declare const RESCUE_SHAKE_CONFIG: ShakeConfig;
41
+ /** A located eligible region. */
42
+ export interface ToolResultShakeRegion {
43
+ kind: "toolResult";
44
+ entry: SessionMessageEntry;
45
+ tokens: number;
46
+ originalText: string;
47
+ /** Human label for the offload doc (tool name). */
48
+ label: string;
49
+ }
50
+ export interface BlockShakeRegion {
51
+ kind: "block";
52
+ entry: SessionMessageEntry | CustomMessageEntry;
53
+ /** Index into the content array, or -1 for string-form content. */
54
+ blockIndex: number;
55
+ /** Character offsets into the target text (start inclusive, end exclusive). */
56
+ start: number;
57
+ end: number;
58
+ tokens: number;
59
+ originalText: string;
60
+ /** Human label for the offload doc (role / customType). */
61
+ label: string;
62
+ }
63
+ export type ShakeRegion = ToolResultShakeRegion | BlockShakeRegion;
64
+ /**
65
+ * Pure detection: locate every eligible shake region on a branch.
66
+ *
67
+ * Walks the protect-recent window (most recent `protectTokens` of context is
68
+ * kept intact), collects whole tool-result messages (honoring `protectedTools`
69
+ * and skipping already-pruned results) and large fenced/XML blocks inside
70
+ * user/developer/assistant/custom messages. Tool results flagged contextually
71
+ * useless by their tool bypass the protect window — there is nothing recent
72
+ * worth keeping in them. Returns regions in document order.
73
+ *
74
+ * `toolCall` blocks are never touched (tool-call/result pairing is preserved)
75
+ * and regions never span a message boundary. When the combined estimated
76
+ * savings is below `minSavings`, returns `[]` (no-op).
77
+ */
78
+ export declare function collectShakeRegions(entries: SessionEntry[], config: ShakeConfig): ShakeRegion[];
79
+ /**
80
+ * Pure mutation: replace a single region's content in place.
81
+ *
82
+ * Tool-result: replaces the message content with the placeholder text and
83
+ * stamps `prunedAt`. Block: splices `replacement` over `[start, end)` of the
84
+ * target text block. When several block regions share one text block they MUST
85
+ * be applied highest-start-first so earlier offsets stay valid — use
86
+ * {@link applyShakeRegions}, which orders them correctly.
87
+ */
88
+ export declare function applyShakeRegion(region: ShakeRegion, replacement: string): void;
89
+ /**
90
+ * Apply many regions at once. Block regions are applied highest-start-first so
91
+ * that splicing one region never shifts the offsets of another in the same text
92
+ * block; tool-result regions are independent.
93
+ */
94
+ export declare function applyShakeRegions(items: Array<{
95
+ region: ShakeRegion;
96
+ replacement: string;
97
+ }>): void;
@@ -0,0 +1,19 @@
1
+ import type { ToolResultMessage } from "@linxiraos/pi-ai";
2
+ import type { AgentToolCall } from "../types.js";
3
+ import type { SessionEntry } from "./entries.js";
4
+ export interface ProtectedToolContext {
5
+ readonly toolResult: ToolResultMessage;
6
+ readonly toolCall: AgentToolCall | undefined;
7
+ }
8
+ export type ProtectedToolMatcher = string | ((context: ProtectedToolContext) => boolean);
9
+ export declare function collectToolCallsById(entries: readonly SessionEntry[]): Map<string, AgentToolCall>;
10
+ /**
11
+ * Extract the `path` argument from a paired `read` tool call, when the result
12
+ * is a `read` result carrying a string path. Returns `undefined` otherwise.
13
+ * Shared primitive for read-targeted protection matchers (skills, plans, …).
14
+ */
15
+ export declare function getReadToolPath({ toolResult, toolCall }: ProtectedToolContext): string | undefined;
16
+ export declare function isSkillReadToolResult(context: ProtectedToolContext): boolean;
17
+ /** Recovery reads of session artifacts — eliding one only mints another artifact and can repeat indefinitely. */
18
+ export declare function isArtifactRecoveryToolResult(context: ProtectedToolContext): boolean;
19
+ export declare function isProtectedToolResult(toolResult: ToolResultMessage, toolCall: AgentToolCall | undefined, matchers: readonly ProtectedToolMatcher[]): boolean;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared utilities for compaction and branch summarization.
3
+ */
4
+ import type { Message } from "@linxiraos/pi-ai";
5
+ import { type Dialect } from "@linxiraos/pi-ai/dialect";
6
+ import type { AgentMessage } from "../types.js";
7
+ export interface FileOperations {
8
+ read: Set<string>;
9
+ written: Set<string>;
10
+ edited: Set<string>;
11
+ }
12
+ export declare function createFileOps(): FileOperations;
13
+ /**
14
+ * Split a read-tool path into its base path and trailing selector, mirroring the
15
+ * read tool's own splitter. Single source of the grammar in this package: the
16
+ * file-operations list strips selectors via {@link stripReadSelector}, and the
17
+ * supersede-prune pass keys on both parts via `readToolSupersedeKey`.
18
+ */
19
+ export declare function splitReadSelector(path: string): {
20
+ path: string;
21
+ sel?: string;
22
+ };
23
+ /**
24
+ * Strip a trailing read-tool selector (`:50-200`, `:raw`, `:1-50:raw`, `:conflicts`, …)
25
+ * so the same file read with different line ranges dedupes to one `<files>` entry
26
+ * and matches its write/edit path when computing Read/Write/RW markers.
27
+ */
28
+ export declare function stripReadSelector(path: string): string;
29
+ /**
30
+ * Whether `path` references a `scheme://` URL (internal URI or web URL) rather
31
+ * than a filesystem path that belongs in the compaction `<files>` summary.
32
+ */
33
+ export declare function isUrlSchemePath(path: string): boolean;
34
+ /**
35
+ * Extract file operations from tool calls in an assistant message.
36
+ */
37
+ export declare function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void;
38
+ /**
39
+ * Compute final file lists from file operations.
40
+ * Returns readFiles (files only read, not modified) and modifiedFiles.
41
+ */
42
+ export declare function computeFileLists(fileOps: FileOperations): {
43
+ readFiles: string[];
44
+ modifiedFiles: string[];
45
+ };
46
+ export declare function formatFileOperations(readFiles: string[], modifiedFiles: string[], readSet?: ReadonlySet<string>): string;
47
+ export declare function upsertFileOperations(summary: string, readFiles: string[], modifiedFiles: string[], readSet?: ReadonlySet<string>): string;
48
+ /**
49
+ * Truncate tool results to the same representation used in summarization prompts.
50
+ */
51
+ export declare function truncateToolResultForSummary(text: string): string;
52
+ /**
53
+ * Serialize LLM messages as plain summary input without provider control tokens.
54
+ */
55
+ export declare function serializeConversationForSummary(messages: Message[], dialect?: Dialect): string;
56
+ /**
57
+ * Serialize LLM messages to transcript text.
58
+ * Call convertToLlm() first to handle custom message types.
59
+ */
60
+ export declare function serializeConversation(messages: Message[], dialect?: Dialect): string;
61
+ export declare const SUMMARIZATION_SYSTEM_PROMPT: string;
@@ -0,0 +1 @@
1
+ export * from "./compaction/index.js";
@@ -0,0 +1,13 @@
1
+ export * from "./agent.js";
2
+ export * from "./agent-loop.js";
3
+ export * from "./append-only-context.js";
4
+ export * from "./compaction.js";
5
+ export * from "./pause.js";
6
+ export * from "./proxy.js";
7
+ export * from "./replay-policy.js";
8
+ export * from "./run-collector.js";
9
+ export * from "./telemetry.js";
10
+ export * from "./thinking.js";
11
+ export * from "./tokenizer.js";
12
+ export * from "./types.js";
13
+ export * from "./utils/yield.js";
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Process-global pause gate for agent loops.
3
+ *
4
+ * Every agent in a process — main session, in-process subagents, advisor —
5
+ * funnels through {@link ../agent-loop!agentLoop}, which polls this gate at its
6
+ * two action boundaries: before each model call and before each tool call
7
+ * starts. Engaging the gate therefore freezes all of them at the next safe
8
+ * point without aborting anything: in-flight provider streams and
9
+ * already-started tool executions run to completion, then every loop parks
10
+ * until {@link AgentPauseGate.resume}. Queued steering/follow-up messages stay
11
+ * queued and deliver normally after resume.
12
+ *
13
+ * A run's own `AbortSignal` still unwinds a parked loop immediately: the park
14
+ * releases on abort (without releasing the gate), so cancelling one run never
15
+ * requires resuming the whole process.
16
+ *
17
+ * Hosts drive the singleton {@link agentPauseGate} (e.g. the TUI `/pause`
18
+ * command); library code only ever reads it.
19
+ */
20
+ /** Listener invoked with the new state on every pause/resume transition. */
21
+ export type AgentPauseListener = (paused: boolean) => void;
22
+ /** Freeze switch shared by every agent loop in the process. See module docs. */
23
+ export declare class AgentPauseGate {
24
+ #private;
25
+ /** True while the gate is engaged. */
26
+ get paused(): boolean;
27
+ /** Epoch ms when the current pause began; undefined when running. */
28
+ get pausedAt(): number | undefined;
29
+ /** Engage the gate. Returns false (and does nothing) when already paused. */
30
+ pause(): boolean;
31
+ /**
32
+ * Release the gate, waking every parked loop. Returns the pause duration in
33
+ * ms, or undefined when the gate was not engaged.
34
+ */
35
+ resume(): number | undefined;
36
+ /** Subscribe to pause/resume transitions. Returns an unsubscribe function. */
37
+ onChange(listener: AgentPauseListener): () => void;
38
+ /**
39
+ * Park until the gate is released. Resolves immediately when not paused.
40
+ * An abort on `signal` releases only this wait — the gate stays engaged —
41
+ * so a cancelled run unwinds while the rest of the process stays frozen.
42
+ */
43
+ waitUntilResumed(signal?: AbortSignal): Promise<void>;
44
+ }
45
+ /** The process-wide gate polled by the agent loop. */
46
+ export declare const agentPauseGate: AgentPauseGate;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Proxy stream function for apps that route LLM calls through a server.
3
+ * The server manages auth and proxies requests to LLM providers.
4
+ */
5
+ import { type AssistantMessage, type AssistantMessageEvent, type Context, EventStream, type FetchImpl, type ImageContent, type Model, type SimpleStreamOptions, type StopReason } from "@linxiraos/pi-ai";
6
+ export declare class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
7
+ constructor();
8
+ }
9
+ /**
10
+ * Proxy event types - server sends these with partial field stripped to reduce bandwidth.
11
+ */
12
+ export type ProxyAssistantMessageEvent = {
13
+ type: "start";
14
+ } | {
15
+ type: "text_start";
16
+ contentIndex: number;
17
+ } | {
18
+ type: "text_delta";
19
+ contentIndex: number;
20
+ delta: string;
21
+ } | {
22
+ type: "text_end";
23
+ contentIndex: number;
24
+ contentSignature?: string;
25
+ } | {
26
+ type: "thinking_start";
27
+ contentIndex: number;
28
+ } | {
29
+ type: "thinking_delta";
30
+ contentIndex: number;
31
+ delta: string;
32
+ } | {
33
+ type: "thinking_end";
34
+ contentIndex: number;
35
+ contentSignature?: string;
36
+ } | {
37
+ type: "image_end";
38
+ contentIndex: number;
39
+ content: ImageContent;
40
+ } | {
41
+ type: "toolcall_start";
42
+ contentIndex: number;
43
+ id: string;
44
+ toolName: string;
45
+ } | {
46
+ type: "toolcall_delta";
47
+ contentIndex: number;
48
+ delta: string;
49
+ } | {
50
+ type: "toolcall_end";
51
+ contentIndex: number;
52
+ } | {
53
+ type: "done";
54
+ reason: Extract<StopReason, "stop" | "length" | "toolUse">;
55
+ usage: AssistantMessage["usage"];
56
+ content?: AssistantMessage["content"];
57
+ } | {
58
+ type: "error";
59
+ reason: Extract<StopReason, "aborted" | "error">;
60
+ errorMessage?: string;
61
+ usage: AssistantMessage["usage"];
62
+ content?: AssistantMessage["content"];
63
+ };
64
+ export interface ProxyStreamOptions extends SimpleStreamOptions {
65
+ /** Auth token for the proxy server */
66
+ authToken: string;
67
+ /** Proxy server URL (e.g., "https://genai.example.com") */
68
+ proxyUrl: string;
69
+ /** Optional fetch implementation; defaults to global fetch. */
70
+ fetch?: FetchImpl;
71
+ }
72
+ /**
73
+ * Stream function that proxies through a server instead of calling LLM providers directly.
74
+ * The server strips the partial field from delta events to reduce bandwidth.
75
+ * We reconstruct the partial message client-side.
76
+ *
77
+ * Use this as the `streamFn` option when creating an Agent that needs to go through a proxy.
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const agent = new Agent({
82
+ * streamFn: (model, context, options) =>
83
+ * streamProxy(model, context, {
84
+ * ...options,
85
+ * authToken: await getAuthToken(),
86
+ * proxyUrl: "https://genai.example.com",
87
+ * }),
88
+ * });
89
+ * ```
90
+ */
91
+ export declare function streamProxy(model: Model, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream;
@@ -0,0 +1,5 @@
1
+ import type { AssistantMessage, Message } from "@linxiraos/pi-ai";
2
+ /** Detects API-level provider refusals that are terminal errors, not dialogue to replay. */
3
+ export declare function isProviderRefusalMessage(message: AssistantMessage): boolean;
4
+ /** Removes API-level provider refusals from live provider replay while preserving other messages. */
5
+ export declare function filterProviderReplayMessages(messages: readonly Message[]): Message[];