jeopi-agent-core 16.2.13
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.
- package/CHANGELOG.md +1016 -0
- package/README.md +473 -0
- package/dist/types/agent-loop.d.ts +66 -0
- package/dist/types/agent.d.ts +427 -0
- package/dist/types/append-only-context.d.ts +133 -0
- package/dist/types/compaction/branch-summarization.d.ts +101 -0
- package/dist/types/compaction/compaction-v2-streaming.d.ts +82 -0
- package/dist/types/compaction/compaction.d.ts +283 -0
- package/dist/types/compaction/entries.d.ts +110 -0
- package/dist/types/compaction/errors.d.ts +26 -0
- package/dist/types/compaction/index.d.ts +12 -0
- package/dist/types/compaction/messages.d.ts +77 -0
- package/dist/types/compaction/openai.d.ts +77 -0
- package/dist/types/compaction/pruning.d.ts +105 -0
- package/dist/types/compaction/shake.d.ts +92 -0
- package/dist/types/compaction/tool-protection.d.ts +17 -0
- package/dist/types/compaction/utils.d.ts +58 -0
- package/dist/types/compaction.d.ts +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/proxy.d.ts +85 -0
- package/dist/types/replay-policy.d.ts +5 -0
- package/dist/types/run-collector.d.ts +196 -0
- package/dist/types/telemetry.d.ts +590 -0
- package/dist/types/thinking.d.ts +17 -0
- package/dist/types/tokenizer.d.ts +1 -0
- package/dist/types/types.d.ts +640 -0
- package/dist/types/utils/yield.d.ts +71 -0
- package/package.json +78 -0
- package/src/agent-loop.ts +2188 -0
- package/src/agent.ts +1457 -0
- package/src/append-only-context.ts +348 -0
- package/src/compaction/branch-summarization.ts +370 -0
- package/src/compaction/compaction-v2-streaming.ts +719 -0
- package/src/compaction/compaction.ts +1553 -0
- package/src/compaction/entries.ts +142 -0
- package/src/compaction/errors.ts +31 -0
- package/src/compaction/index.ts +13 -0
- package/src/compaction/messages.ts +237 -0
- package/src/compaction/openai.ts +581 -0
- package/src/compaction/prompts/auto-handoff-threshold-focus.md +1 -0
- package/src/compaction/prompts/branch-summary-context.md +5 -0
- package/src/compaction/prompts/branch-summary-preamble.md +2 -0
- package/src/compaction/prompts/branch-summary.md +30 -0
- package/src/compaction/prompts/compaction-short-summary.md +9 -0
- package/src/compaction/prompts/compaction-summary-context.md +5 -0
- package/src/compaction/prompts/compaction-summary.md +38 -0
- package/src/compaction/prompts/compaction-turn-prefix.md +17 -0
- package/src/compaction/prompts/compaction-update-summary.md +45 -0
- package/src/compaction/prompts/file-operations.md +5 -0
- package/src/compaction/prompts/handoff-document.md +49 -0
- package/src/compaction/prompts/snapcompact-archive-context.md +3 -0
- package/src/compaction/prompts/summarization-system.md +3 -0
- package/src/compaction/pruning.ts +424 -0
- package/src/compaction/shake.ts +429 -0
- package/src/compaction/tool-protection.ts +55 -0
- package/src/compaction/utils.ts +323 -0
- package/src/compaction.ts +1 -0
- package/src/index.ts +24 -0
- package/src/proxy.ts +376 -0
- package/src/replay-policy.ts +13 -0
- package/src/run-collector.ts +631 -0
- package/src/telemetry.ts +2034 -0
- package/src/thinking.ts +19 -0
- package/src/tokenizer.ts +17 -0
- package/src/types.ts +718 -0
- package/src/utils/yield.ts +183 -0
|
@@ -0,0 +1,82 @@
|
|
|
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 { FetchImpl, Model } from "jeopi-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
|
+
}): Promise<CompactionV2Response>;
|
|
70
|
+
/** Build Codex-style V2 replacement history from prompt input plus compaction output. */
|
|
71
|
+
export declare function buildCompactionV2ReplacementHistory(input: unknown[], compactionItem: Record<string, unknown>, retainedMessageBudget?: number): {
|
|
72
|
+
replacementHistory: Array<Record<string, unknown>>;
|
|
73
|
+
retainedImageCount: number;
|
|
74
|
+
};
|
|
75
|
+
/** Store V2 replacement history in the OpenAI remote-compaction preserve slot. */
|
|
76
|
+
export declare function storeCompactionV2PreserveData(response: CompactionV2Response, model: Model): Record<string, unknown>;
|
|
77
|
+
/** Retrieve preserved OpenAI replacement history that V2 can extend. */
|
|
78
|
+
export declare function getCompactionV2PreserveData(preserveData: Record<string, unknown> | undefined): {
|
|
79
|
+
provider: string;
|
|
80
|
+
replacementHistory: Array<Record<string, unknown>>;
|
|
81
|
+
usedTokens: number;
|
|
82
|
+
} | undefined;
|
|
@@ -0,0 +1,283 @@
|
|
|
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 Context, type FetchImpl, type MessageAttribution, type Model, type SimpleStreamOptions, type Tool, type Usage } from "jeopi-ai";
|
|
8
|
+
import { type AgentTelemetry } from "../telemetry";
|
|
9
|
+
import { ThinkingLevel } from "../thinking";
|
|
10
|
+
import type { AgentMessage } from "../types";
|
|
11
|
+
import type { SessionEntry } from "./entries";
|
|
12
|
+
import { type ConvertToLlm } from "./messages";
|
|
13
|
+
import { type FileOperations } from "./utils";
|
|
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
|
+
export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
|
|
56
|
+
/**
|
|
57
|
+
* Calculate total context tokens from usage.
|
|
58
|
+
* Uses the native totalTokens field when available, falls back to computing from components.
|
|
59
|
+
*/
|
|
60
|
+
export declare function calculateContextTokens(usage: Usage): number;
|
|
61
|
+
export declare function calculatePromptTokens(usage: Usage): number;
|
|
62
|
+
/**
|
|
63
|
+
* Find the last non-aborted assistant message usage from session entries.
|
|
64
|
+
*/
|
|
65
|
+
export declare function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Effective reserve: at least 15% of context window or the configured floor
|
|
68
|
+
* (defaulting to {@link DEFAULT_RESERVE_TOKENS} when unset), whichever is larger.
|
|
69
|
+
*/
|
|
70
|
+
export declare function effectiveReserveTokens(contextWindow: number, settings: CompactionSettings): number;
|
|
71
|
+
/**
|
|
72
|
+
* Reserve used when deciding whether a prompt still fits inside the model window.
|
|
73
|
+
*
|
|
74
|
+
* The default absolute reserve predates small bundled windows and can leave no
|
|
75
|
+
* practical budget there; recover a DEFAULTED reserve that is impossible for
|
|
76
|
+
* the window with the 15% proportional reserve (clamped to >= 1 so the derived
|
|
77
|
+
* threshold stays strictly below the window even for tiny test windows).
|
|
78
|
+
* Explicit valid reserves — including one that happens to equal the default —
|
|
79
|
+
* still win, because they intentionally shrink the usable prompt budget;
|
|
80
|
+
* provenance is carried by `settings.reserveTokens` being unset, never by
|
|
81
|
+
* comparing values against the default.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolveBudgetReserveTokens(contextWindow: number, settings: CompactionSettings): number;
|
|
84
|
+
/**
|
|
85
|
+
* Check if compaction should trigger based on context usage.
|
|
86
|
+
*/
|
|
87
|
+
export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Context tokens to feed the compaction decision, floored by a local estimate of
|
|
90
|
+
* the stored conversation.
|
|
91
|
+
*
|
|
92
|
+
* The provider-reported usage is normally ground truth, but a
|
|
93
|
+
* `before_provider_request` payload transform — a compression extension (e.g.
|
|
94
|
+
* Headroom), an obfuscator, or inline snapcompact — can shrink the request below
|
|
95
|
+
* the real stored conversation. The provider then reports deflated prompt
|
|
96
|
+
* tokens, so anchoring compaction purely on that usage lets the real history
|
|
97
|
+
* grow unbounded until it overflows and native compaction can no longer run.
|
|
98
|
+
* Flooring by the agent's own estimate of the stored conversation keeps the
|
|
99
|
+
* compaction trigger honest regardless of on-wire compression. (Display/cost
|
|
100
|
+
* accounting still uses the exact provider usage; only the compaction decision
|
|
101
|
+
* takes the floor.)
|
|
102
|
+
*/
|
|
103
|
+
export declare function compactionContextTokens(providerContextTokens: number, storedConversationEstimate: number): number;
|
|
104
|
+
export declare function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings): number;
|
|
105
|
+
/**
|
|
106
|
+
* Estimate token count for a message using cl100k_base via the native
|
|
107
|
+
* tokenizer. This is not Claude's first-party tokenizer (Anthropic doesn't
|
|
108
|
+
* publish one) but is within ~5–10% across English/code text.
|
|
109
|
+
*
|
|
110
|
+
* `excludeEncryptedReasoning` drops opaque provider reasoning payloads
|
|
111
|
+
* (`thinkingSignature`, `redactedThinking`) from the estimate. Those are billed
|
|
112
|
+
* by the provider on replay, so the default counts them — but their *local*
|
|
113
|
+
* byte size can diverge wildly from what the provider charges, so the
|
|
114
|
+
* compaction floor (which only needs the reliably-countable, on-wire-compressible
|
|
115
|
+
* content) excludes them to avoid false triggers on thinking-heavy turns.
|
|
116
|
+
*/
|
|
117
|
+
export declare function estimateTokens(message: AgentMessage, options?: {
|
|
118
|
+
excludeEncryptedReasoning?: boolean;
|
|
119
|
+
}): number;
|
|
120
|
+
/**
|
|
121
|
+
* Find the user message (or bashExecution) that starts the turn containing the given entry index.
|
|
122
|
+
* Returns -1 if no turn start found before the index.
|
|
123
|
+
* BashExecutionMessage is treated like a user message for turn boundaries.
|
|
124
|
+
*/
|
|
125
|
+
export declare function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number;
|
|
126
|
+
export interface CutPointResult {
|
|
127
|
+
/** Index of first entry to keep */
|
|
128
|
+
firstKeptEntryIndex: number;
|
|
129
|
+
/** Index of user message that starts the turn being split, or -1 if not splitting */
|
|
130
|
+
turnStartIndex: number;
|
|
131
|
+
/** Whether this cut splits a turn (cut point is not a user message) */
|
|
132
|
+
isSplitTurn: boolean;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Find the cut point in session entries that keeps approximately `keepRecentTokens`.
|
|
136
|
+
*
|
|
137
|
+
* Algorithm: Walk backwards from newest, accumulating estimated message sizes.
|
|
138
|
+
* Stop when we've accumulated >= keepRecentTokens. Cut at that point.
|
|
139
|
+
*
|
|
140
|
+
* Can cut at user OR assistant messages (never tool results). When cutting at an
|
|
141
|
+
* assistant message with tool calls, its tool results come after and will be kept.
|
|
142
|
+
*
|
|
143
|
+
* Returns CutPointResult with:
|
|
144
|
+
* - firstKeptEntryIndex: the entry index to start keeping from
|
|
145
|
+
* - turnStartIndex: if cutting mid-turn, the user message that started that turn
|
|
146
|
+
* - isSplitTurn: whether we're cutting in the middle of a turn
|
|
147
|
+
*
|
|
148
|
+
* Only considers entries between `startIndex` and `endIndex` (exclusive).
|
|
149
|
+
*/
|
|
150
|
+
export declare function findCutPoint(entries: SessionEntry[], startIndex: number, endIndex: number, keepRecentTokens: number): CutPointResult;
|
|
151
|
+
export declare const AUTO_HANDOFF_THRESHOLD_FOCUS: string;
|
|
152
|
+
/**
|
|
153
|
+
* Generate a summary of the conversation using the LLM.
|
|
154
|
+
* If previousSummary is provided, uses the update prompt to merge.
|
|
155
|
+
*/
|
|
156
|
+
export interface SummaryOptions {
|
|
157
|
+
promptOverride?: string;
|
|
158
|
+
extraContext?: string[];
|
|
159
|
+
remoteEndpoint?: string;
|
|
160
|
+
remoteInstructions?: string;
|
|
161
|
+
initiatorOverride?: MessageAttribution;
|
|
162
|
+
metadata?: Record<string, unknown>;
|
|
163
|
+
convertToLlm?: ConvertToLlm;
|
|
164
|
+
/**
|
|
165
|
+
* Optional telemetry handle. When provided, every LLM call emitted during
|
|
166
|
+
* compaction is wrapped in an OTEL chat span tagged with
|
|
167
|
+
* `pi.gen_ai.oneshot.kind` (`compaction_summary`, `compaction_short_summary`,
|
|
168
|
+
* or `compaction_turn_prefix`). `undefined` keeps the call paths zero-cost.
|
|
169
|
+
*/
|
|
170
|
+
telemetry?: AgentTelemetry;
|
|
171
|
+
/**
|
|
172
|
+
* Active session thinking level. Threaded from `agent-session.ts` so
|
|
173
|
+
* compaction honors the user's `/model` thinking selection instead of
|
|
174
|
+
* silently overriding it with `Effort.High` (the historical default).
|
|
175
|
+
* `undefined` / `ThinkingLevel.Inherit` falls back to that historical
|
|
176
|
+
* default; `ThinkingLevel.Off` omits reasoning entirely. See
|
|
177
|
+
* `resolveCompactionEffort` for the conversion contract.
|
|
178
|
+
*/
|
|
179
|
+
thinkingLevel?: ThinkingLevel;
|
|
180
|
+
/** Session routing key for remote compaction transports with sticky provider sessions. */
|
|
181
|
+
sessionId?: string;
|
|
182
|
+
/** Prompt-cache key for remote compaction transports that support provider prefix caching. */
|
|
183
|
+
promptCacheKey?: string;
|
|
184
|
+
/** Provider-visible tools for remote compaction transports that replay native tool history. */
|
|
185
|
+
tools?: Tool[];
|
|
186
|
+
/** Optional fetch implementation threaded into remote compaction calls. */
|
|
187
|
+
fetch?: FetchImpl;
|
|
188
|
+
/**
|
|
189
|
+
* Optional completion transport override for host-level request wrappers
|
|
190
|
+
* (e.g. the coding-agent provider-concurrency limiter). When provided,
|
|
191
|
+
* every local summarization oneshot (`generateSummary`,
|
|
192
|
+
* `generateTurnPrefixSummary`, `generateShortSummary`) routes through it
|
|
193
|
+
* instead of the default `completeSimple`, so cap policies enforced on
|
|
194
|
+
* the live agent turn also bracket compaction HTTP requests.
|
|
195
|
+
*/
|
|
196
|
+
completeImpl?: <TApi extends Api>(model: Model<TApi>, ctx: Context, options: SimpleStreamOptions) => Promise<AssistantMessage>;
|
|
197
|
+
}
|
|
198
|
+
export declare function generateSummary(currentMessages: AgentMessage[], model: Model, reserveTokens: number, apiKey: ApiKey, signal?: AbortSignal, customInstructions?: string, previousSummary?: string, options?: SummaryOptions): Promise<string>;
|
|
199
|
+
export interface HandoffOptions {
|
|
200
|
+
/** Live agent system prompt — passed verbatim so providers hit the cached prefix. */
|
|
201
|
+
systemPrompt: string[];
|
|
202
|
+
/** Live agent tool list — same purpose. Forced to `toolChoice: "none"`. */
|
|
203
|
+
tools?: Tool[];
|
|
204
|
+
customInstructions?: string;
|
|
205
|
+
convertToLlm?: ConvertToLlm;
|
|
206
|
+
initiatorOverride?: MessageAttribution;
|
|
207
|
+
metadata?: Record<string, unknown>;
|
|
208
|
+
/**
|
|
209
|
+
* Optional telemetry handle. When provided, the handoff LLM call is
|
|
210
|
+
* wrapped in an OTEL chat span tagged with `pi.gen_ai.oneshot.kind = "handoff"`.
|
|
211
|
+
*/
|
|
212
|
+
telemetry?: AgentTelemetry;
|
|
213
|
+
/**
|
|
214
|
+
* Active session thinking level. Threaded from `agent-session.ts` so
|
|
215
|
+
* handoff generation honors the user's `/model` thinking selection
|
|
216
|
+
* instead of silently overriding it with `Effort.High`. See
|
|
217
|
+
* `resolveCompactionEffort` for the conversion contract.
|
|
218
|
+
*/
|
|
219
|
+
thinkingLevel?: ThinkingLevel;
|
|
220
|
+
}
|
|
221
|
+
export declare function renderHandoffPrompt(customInstructions?: string): string;
|
|
222
|
+
export interface HandoffFromContextOptions {
|
|
223
|
+
/**
|
|
224
|
+
* Stream options mirrored from the live agent turn: `apiKey`, `signal`, the
|
|
225
|
+
* `sessionId`/`promptCacheKey` cache-routing pair, `serviceTier`, and the
|
|
226
|
+
* session's payload/response hooks. Sending the same routing + payload shape
|
|
227
|
+
* the main loop uses is what lets the handoff oneshot READ the provider
|
|
228
|
+
* prompt cache the live turn populated instead of cold-missing the whole
|
|
229
|
+
* prefix. `reasoning` and `toolChoice` are set internally and override
|
|
230
|
+
* anything provided here.
|
|
231
|
+
*/
|
|
232
|
+
streamOptions: SimpleStreamOptions;
|
|
233
|
+
/** Optional completion transport override for host-level request wrappers. */
|
|
234
|
+
completeImpl?: <TApi extends Api>(model: Model<TApi>, ctx: Context, options: SimpleStreamOptions) => Promise<AssistantMessage>;
|
|
235
|
+
/** See {@link HandoffOptions.telemetry}. */
|
|
236
|
+
telemetry?: AgentTelemetry;
|
|
237
|
+
/** See {@link HandoffOptions.thinkingLevel}. */
|
|
238
|
+
thinkingLevel?: ThinkingLevel;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Run the handoff oneshot against a fully-built provider {@link Context}.
|
|
242
|
+
*
|
|
243
|
+
* The caller assembles `context` exactly like a live agent turn — same system
|
|
244
|
+
* prompt, normalized tools, transformed + obfuscated message history, with the
|
|
245
|
+
* trailing handoff-prompt message already appended — and supplies
|
|
246
|
+
* `streamOptions` that mirror the live turn's cache routing. That keeps the
|
|
247
|
+
* cache-preserving context construction in the host (which owns the transform
|
|
248
|
+
* pipeline) while this function centralizes the handoff request contract:
|
|
249
|
+
* `toolChoice: "none"`, clamped reasoning effort, oneshot telemetry, text-only
|
|
250
|
+
* extraction, and provider-error mapping.
|
|
251
|
+
*/
|
|
252
|
+
export declare function generateHandoffFromContext(context: Context, model: Model, options: HandoffFromContextOptions): Promise<string>;
|
|
253
|
+
export declare function generateHandoff(messages: AgentMessage[], model: Model, apiKey: ApiKey, options: HandoffOptions, signal?: AbortSignal): Promise<string>;
|
|
254
|
+
export interface CompactionPreparation {
|
|
255
|
+
/** UUID of first entry to keep */
|
|
256
|
+
firstKeptEntryId: string;
|
|
257
|
+
/** Messages that will be summarized and discarded */
|
|
258
|
+
messagesToSummarize: AgentMessage[];
|
|
259
|
+
/** Messages that will be turned into turn prefix summary (if splitting) */
|
|
260
|
+
turnPrefixMessages: AgentMessage[];
|
|
261
|
+
/** Messages kept in full after compaction (recent history) */
|
|
262
|
+
recentMessages: AgentMessage[];
|
|
263
|
+
/** Whether this is a split turn (cut point in middle of turn) */
|
|
264
|
+
isSplitTurn: boolean;
|
|
265
|
+
tokensBefore: number;
|
|
266
|
+
/** Summary from previous compaction, for iterative update */
|
|
267
|
+
previousSummary?: string;
|
|
268
|
+
/** Preserved opaque compaction payload from the previous compaction, if any. */
|
|
269
|
+
previousPreserveData?: Record<string, unknown>;
|
|
270
|
+
/** File operations extracted from messagesToSummarize */
|
|
271
|
+
fileOps: FileOperations;
|
|
272
|
+
/** Compaction settions from settings.jsonl */
|
|
273
|
+
settings: CompactionSettings;
|
|
274
|
+
}
|
|
275
|
+
export declare function prepareCompaction(pathEntries: SessionEntry[], settings: CompactionSettings, compactionModels?: readonly Model[]): CompactionPreparation | undefined;
|
|
276
|
+
/**
|
|
277
|
+
* Generate summaries for compaction using prepared data.
|
|
278
|
+
* Returns CompactionResult - SessionManager adds id/parentId when saving.
|
|
279
|
+
*
|
|
280
|
+
* @param preparation - Pre-calculated preparation from prepareCompaction()
|
|
281
|
+
* @param customInstructions - Optional custom focus for the summary
|
|
282
|
+
*/
|
|
283
|
+
export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: ApiKey, customInstructions?: string, signal?: AbortSignal, options?: SummaryOptions): Promise<CompactionResult>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { ImageContent, MessageAttribution, ServiceTierByFamily, TextContent } from "jeopi-ai";
|
|
2
|
+
import type { AgentMessage } from "../types";
|
|
3
|
+
export interface SessionEntryBase {
|
|
4
|
+
type: string;
|
|
5
|
+
id: string;
|
|
6
|
+
parentId: string | null;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SessionMessageEntry extends SessionEntryBase {
|
|
10
|
+
type: "message";
|
|
11
|
+
message: AgentMessage;
|
|
12
|
+
}
|
|
13
|
+
export interface ThinkingLevelChangeEntry extends SessionEntryBase {
|
|
14
|
+
type: "thinking_level_change";
|
|
15
|
+
thinkingLevel?: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface ModelChangeEntry extends SessionEntryBase {
|
|
18
|
+
type: "model_change";
|
|
19
|
+
/** Model in "provider/modelId" format */
|
|
20
|
+
model: string;
|
|
21
|
+
/** Role: "default", "smol", "slow", etc. Undefined treated as "default" */
|
|
22
|
+
role?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ServiceTierChangeEntry extends SessionEntryBase {
|
|
25
|
+
type: "service_tier_change";
|
|
26
|
+
serviceTier: ServiceTierByFamily | null;
|
|
27
|
+
}
|
|
28
|
+
export interface CompactionEntry<T = unknown> extends SessionEntryBase {
|
|
29
|
+
type: "compaction";
|
|
30
|
+
summary: string;
|
|
31
|
+
shortSummary?: string;
|
|
32
|
+
firstKeptEntryId: string;
|
|
33
|
+
tokensBefore: number;
|
|
34
|
+
/** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
|
|
35
|
+
details?: T;
|
|
36
|
+
/** Hook-provided data to persist across compaction */
|
|
37
|
+
preserveData?: Record<string, unknown>;
|
|
38
|
+
/** True if generated by an extension, undefined/false if pi-generated (backward compatible) */
|
|
39
|
+
fromExtension?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface BranchSummaryEntry<T = unknown> extends SessionEntryBase {
|
|
42
|
+
type: "branch_summary";
|
|
43
|
+
fromId: string;
|
|
44
|
+
summary: string;
|
|
45
|
+
/** Extension-specific data (not sent to LLM) */
|
|
46
|
+
details?: T;
|
|
47
|
+
/** True if generated by an extension, false if pi-generated */
|
|
48
|
+
fromExtension?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface CustomMessageEntry<T = unknown> extends SessionEntryBase {
|
|
51
|
+
type: "custom_message";
|
|
52
|
+
customType: string;
|
|
53
|
+
content: string | (TextContent | ImageContent)[];
|
|
54
|
+
details?: T;
|
|
55
|
+
display: boolean;
|
|
56
|
+
/** Who initiated this message for billing/attribution semantics. */
|
|
57
|
+
attribution?: MessageAttribution;
|
|
58
|
+
}
|
|
59
|
+
export interface CustomEntry<T = unknown> extends SessionEntryBase {
|
|
60
|
+
type: "custom";
|
|
61
|
+
customType: string;
|
|
62
|
+
data?: T;
|
|
63
|
+
}
|
|
64
|
+
export interface LabelEntry extends SessionEntryBase {
|
|
65
|
+
type: "label";
|
|
66
|
+
targetId: string;
|
|
67
|
+
label: string | undefined;
|
|
68
|
+
}
|
|
69
|
+
export interface TitleChangeEntry extends SessionEntryBase {
|
|
70
|
+
type: "title_change";
|
|
71
|
+
title: string;
|
|
72
|
+
previousTitle?: string;
|
|
73
|
+
source: "auto" | "user";
|
|
74
|
+
trigger?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface TtsrInjectionEntry extends SessionEntryBase {
|
|
77
|
+
type: "ttsr_injection";
|
|
78
|
+
/** Names of rules that were injected */
|
|
79
|
+
injectedRules: string[];
|
|
80
|
+
}
|
|
81
|
+
export interface MCPToolSelectionEntry extends SessionEntryBase {
|
|
82
|
+
type: "mcp_tool_selection";
|
|
83
|
+
/** MCP tool names selected for visibility in discovery mode. */
|
|
84
|
+
selectedToolNames: string[];
|
|
85
|
+
}
|
|
86
|
+
export interface SessionInitEntry extends SessionEntryBase {
|
|
87
|
+
type: "session_init";
|
|
88
|
+
/** Full system prompt sent to the model */
|
|
89
|
+
systemPrompt: string;
|
|
90
|
+
/** Initial task/user message */
|
|
91
|
+
task: string;
|
|
92
|
+
/** Tools available to the agent */
|
|
93
|
+
tools: string[];
|
|
94
|
+
/** Output schema if structured output was requested */
|
|
95
|
+
outputSchema?: unknown;
|
|
96
|
+
}
|
|
97
|
+
export interface ModeChangeEntry extends SessionEntryBase {
|
|
98
|
+
type: "mode_change";
|
|
99
|
+
/** Current mode name, or "none" when exiting a mode */
|
|
100
|
+
mode: string;
|
|
101
|
+
/** Optional mode-specific data (e.g. plan file path) */
|
|
102
|
+
data?: Record<string, unknown>;
|
|
103
|
+
}
|
|
104
|
+
export interface CustomCompactionSessionEntries {
|
|
105
|
+
}
|
|
106
|
+
export type SessionEntry = SessionMessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | ServiceTierChangeEntry | CompactionEntry | BranchSummaryEntry | CustomEntry | CustomMessageEntry | LabelEntry | TitleChangeEntry | TtsrInjectionEntry | MCPToolSelectionEntry | SessionInitEntry | ModeChangeEntry | CustomCompactionSessionEntries[keyof CustomCompactionSessionEntries];
|
|
107
|
+
export interface ReadonlySessionManager {
|
|
108
|
+
getBranch(leafId?: string | null): SessionEntry[];
|
|
109
|
+
getEntry(id: string): SessionEntry | undefined;
|
|
110
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction error types.
|
|
3
|
+
*
|
|
4
|
+
* `CompactionCancelledError` is the canonical signal raised when a compaction
|
|
5
|
+
* is explicitly aborted — operator Esc, extension hook returning `cancel`,
|
|
6
|
+
* programmatic `session.abortCompaction()` call, or any other deliberate
|
|
7
|
+
* abort source. Downstream callers (e.g. `executeCompaction`) discriminate
|
|
8
|
+
* cancellation from other failures via `instanceof CompactionCancelledError`
|
|
9
|
+
* rather than introspecting error messages or `name` fields — the typed
|
|
10
|
+
* sentinel makes classification source-agnostic and refactor-stable.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CompactionCancelledError extends Error {
|
|
13
|
+
readonly name: "CompactionCancelledError";
|
|
14
|
+
constructor(message?: string);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Outcome of a compaction attempt, surfaced by `CommandController.executeCompaction`
|
|
18
|
+
* so callers (e.g. the plan-mode approval flow) can distinguish a deliberate abort
|
|
19
|
+
* from an unrelated failure.
|
|
20
|
+
*
|
|
21
|
+
* "ok" — compaction completed; transcript was summarized.
|
|
22
|
+
* "cancelled" — `CompactionCancelledError` was raised. Operator Esc, extension
|
|
23
|
+
* hook, programmatic abort — all source-agnostic.
|
|
24
|
+
* "failed" — any other rejection from `session.compact()`.
|
|
25
|
+
*/
|
|
26
|
+
export type CompactionOutcome = "ok" | "cancelled" | "failed";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction and summarization utilities.
|
|
3
|
+
*/
|
|
4
|
+
export * from "./branch-summarization";
|
|
5
|
+
export * from "./compaction";
|
|
6
|
+
export * from "./entries";
|
|
7
|
+
export * from "./errors";
|
|
8
|
+
export * from "./messages";
|
|
9
|
+
export * from "./openai";
|
|
10
|
+
export * from "./pruning";
|
|
11
|
+
export * from "./shake";
|
|
12
|
+
export * from "./utils";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ImageContent, Message, MessageAttribution, ProviderPayload, TextContent } from "jeopi-ai";
|
|
2
|
+
import type { AgentMessage } from "../types";
|
|
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
|
+
timestamp: number;
|
|
43
|
+
}
|
|
44
|
+
export type CoreCompactionMessage = CustomMessage | HookMessage | BranchSummaryMessage | CompactionSummaryMessage;
|
|
45
|
+
declare module "../types" {
|
|
46
|
+
interface CustomAgentMessages {
|
|
47
|
+
custom: CustomMessage;
|
|
48
|
+
hookMessage: HookMessage;
|
|
49
|
+
branchSummary: BranchSummaryMessage;
|
|
50
|
+
compactionSummary: CompactionSummaryMessage;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export type ConvertToLlm = (messages: AgentMessage[]) => Message[];
|
|
54
|
+
export declare function renderBranchSummaryContext(summary: string): string;
|
|
55
|
+
export declare function renderCompactionSummaryContext(summary: string): string;
|
|
56
|
+
export declare function createBranchSummaryMessage(summary: string, fromId: string, timestamp: string): BranchSummaryMessage;
|
|
57
|
+
export declare function createCompactionSummaryMessage(summary: string, tokensBefore: number, timestamp: string, shortSummary?: string, providerPayload?: ProviderPayload, images?: ImageContent[], blocks?: (TextContent | ImageContent)[]): CompactionSummaryMessage;
|
|
58
|
+
export declare function createCustomMessage(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details: unknown | undefined, timestamp: string, attribution?: MessageAttribution): CustomMessage;
|
|
59
|
+
/**
|
|
60
|
+
* Transform a single core-domain agent message to its LLM form; `undefined`
|
|
61
|
+
* drops it from the provider request.
|
|
62
|
+
*
|
|
63
|
+
* Single source of truth for the core roles (user/developer/assistant/
|
|
64
|
+
* toolResult) and the compaction messages owned by this package. Embedders
|
|
65
|
+
* with their own app messages (e.g. the coding agent) handle their custom
|
|
66
|
+
* roles and delegate every core role here — duplicating these cases is how
|
|
67
|
+
* snapcompact frames once silently fell off the provider request.
|
|
68
|
+
*/
|
|
69
|
+
export declare function convertMessageToLlm(message: AgentMessage): Message | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Default compaction-domain transformer.
|
|
72
|
+
*
|
|
73
|
+
* Embedders with their own app messages should pass a richer transformer through
|
|
74
|
+
* `SummaryOptions.convertToLlm`; this default intentionally preserves only the
|
|
75
|
+
* core LLM roles and the compaction messages owned by this package.
|
|
76
|
+
*/
|
|
77
|
+
export declare function defaultConvertToLlm(messages: AgentMessage[]): Message[];
|
|
@@ -0,0 +1,77 @@
|
|
|
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 { FetchImpl, Message, Model } from "jeopi-ai/types";
|
|
18
|
+
export * from "./compaction-v2-streaming";
|
|
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 type OpenAiRemoteCompactionItem = {
|
|
30
|
+
type: "compaction" | "compaction_summary";
|
|
31
|
+
encrypted_content?: string;
|
|
32
|
+
summary?: string;
|
|
33
|
+
};
|
|
34
|
+
export interface OpenAiRemoteCompactionPreserveData {
|
|
35
|
+
provider?: string;
|
|
36
|
+
replacementHistory: Array<Record<string, unknown>>;
|
|
37
|
+
compactionItem: OpenAiRemoteCompactionItem;
|
|
38
|
+
}
|
|
39
|
+
export interface OpenAiRemoteCompactionRequest {
|
|
40
|
+
model: string;
|
|
41
|
+
input: Array<Record<string, unknown>>;
|
|
42
|
+
instructions: string;
|
|
43
|
+
}
|
|
44
|
+
export interface OpenAiRemoteCompactionResponse extends OpenAiRemoteCompactionPreserveData {
|
|
45
|
+
}
|
|
46
|
+
export interface RemoteCompactionRequest {
|
|
47
|
+
systemPrompt: string;
|
|
48
|
+
prompt: string;
|
|
49
|
+
}
|
|
50
|
+
export interface RemoteCompactionResponse {
|
|
51
|
+
summary: string;
|
|
52
|
+
shortSummary?: string;
|
|
53
|
+
}
|
|
54
|
+
export declare function shouldUseOpenAiRemoteCompaction(model: Model): boolean;
|
|
55
|
+
export declare function getPreservedOpenAiRemoteCompactionData(preserveData: Record<string, unknown> | undefined): OpenAiRemoteCompactionPreserveData | undefined;
|
|
56
|
+
export declare function withOpenAiRemoteCompactionPreserveData(preserveData: Record<string, unknown> | undefined, remoteCompaction: OpenAiRemoteCompactionPreserveData | undefined): Record<string, unknown> | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Build the OpenAI Responses-API native history array from LLM messages.
|
|
59
|
+
*
|
|
60
|
+
* Caller is responsible for converting any custom message types to
|
|
61
|
+
* `Message[]` first (e.g. via the agent's `convertToLlm`); this function
|
|
62
|
+
* operates purely on the LLM-domain shape.
|
|
63
|
+
*
|
|
64
|
+
* @param messages - LLM messages to encode.
|
|
65
|
+
* @param model - Target model (used for provider gating + tool-call id rules).
|
|
66
|
+
* @param previousReplacementHistory - History from a prior compaction whose
|
|
67
|
+
* encrypted reasoning we want to preserve.
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildOpenAiNativeHistory(messages: Message[], model: Model, previousReplacementHistory?: Array<Record<string, unknown>>): Array<Record<string, unknown>>;
|
|
70
|
+
export declare function requestOpenAiRemoteCompaction(model: Model, apiKey: string, compactInput: Array<Record<string, unknown>>, instructions: string, signal?: AbortSignal, opts?: {
|
|
71
|
+
fetch?: FetchImpl;
|
|
72
|
+
timeoutMs?: number;
|
|
73
|
+
}): Promise<OpenAiRemoteCompactionResponse>;
|
|
74
|
+
export declare function requestRemoteCompaction(endpoint: string, request: RemoteCompactionRequest, signal?: AbortSignal, opts?: {
|
|
75
|
+
fetch?: FetchImpl;
|
|
76
|
+
timeoutMs?: number;
|
|
77
|
+
}): Promise<RemoteCompactionResponse>;
|