@oh-my-pi/pi-agent-core 15.1.0 → 15.1.2
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 +7 -0
- package/dist/types/agent-loop.d.ts +54 -0
- package/dist/types/agent.d.ts +318 -0
- package/dist/types/compaction/branch-summarization.d.ts +88 -0
- package/dist/types/compaction/compaction.d.ts +153 -0
- package/dist/types/compaction/entries.d.ts +103 -0
- package/dist/types/compaction/errors.d.ts +26 -0
- package/dist/types/compaction/index.d.ts +11 -0
- package/dist/types/compaction/messages.d.ts +61 -0
- package/dist/types/compaction/openai.d.ts +58 -0
- package/dist/types/compaction/pruning.d.ts +18 -0
- package/dist/types/compaction/utils.d.ts +32 -0
- package/dist/types/compaction.d.ts +1 -0
- package/dist/types/harmony-leak.d.ts +99 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/proxy.d.ts +84 -0
- package/dist/types/run-collector.d.ts +206 -0
- package/dist/types/telemetry.d.ts +551 -0
- package/dist/types/thinking.d.ts +17 -0
- package/dist/types/types.d.ts +390 -0
- package/package.json +11 -10
- package/src/agent-loop.ts +18 -1
- package/src/telemetry.ts +107 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.1.2] - 2026-05-15
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added `responseHeaders` to `ChatUsageEvent` and `ManualChatTelemetryOptions` so telemetry hooks receive captured lowercase upstream response headers for each chat span
|
|
9
|
+
- Added automatic gateway/proxy detection from response headers (`litellm`, `helicone`, `portkey`, `openrouter`) and stamped `pi.gen_ai.gateway.*` span attributes for detected routing metadata
|
|
10
|
+
- Added exported `detectGatewayFromHeaders` API for header-based gateway detection
|
|
11
|
+
|
|
5
12
|
## [15.1.0] - 2026-05-15
|
|
6
13
|
### Breaking Changes
|
|
7
14
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent loop that works with AgentMessage throughout.
|
|
3
|
+
* Transforms to Message[] only at the LLM call boundary.
|
|
4
|
+
*/
|
|
5
|
+
import { EventStream } from "@oh-my-pi/pi-ai";
|
|
6
|
+
import { type AgentRunCoverage, type AgentRunSummary } from "./run-collector";
|
|
7
|
+
import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, StreamFn } from "./types";
|
|
8
|
+
/**
|
|
9
|
+
* Start an agent loop with a new prompt message.
|
|
10
|
+
* The prompt is added to the context and events are emitted for it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function agentLoop(prompts: AgentMessage[], context: AgentContext, config: AgentLoopConfig, signal?: AbortSignal, streamFn?: StreamFn): EventStream<AgentEvent, AgentMessage[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Continue an agent loop from the current context without adding a new message.
|
|
15
|
+
* Used for retries - context already has user message or tool results.
|
|
16
|
+
*
|
|
17
|
+
* **Important:** The last message in context must convert to a `user` or `toolResult` message
|
|
18
|
+
* via `convertToLlm`. If it doesn't, the LLM provider will reject the request.
|
|
19
|
+
* This cannot be validated here since `convertToLlm` is only called once per turn.
|
|
20
|
+
*/
|
|
21
|
+
export declare function agentLoopContinue(context: AgentContext, config: AgentLoopConfig, signal?: AbortSignal, streamFn?: StreamFn): EventStream<AgentEvent, AgentMessage[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Detailed-result handle returned by {@link agentLoopDetailed}. Adds the
|
|
24
|
+
* run-level telemetry/coverage rollup to the existing `AgentMessage[]`
|
|
25
|
+
* payload without changing the resolved type of `stream.result()`.
|
|
26
|
+
*/
|
|
27
|
+
export interface AgentLoopDetailedResult {
|
|
28
|
+
readonly messages: AgentMessage[];
|
|
29
|
+
readonly telemetry: AgentRunSummary | undefined;
|
|
30
|
+
readonly coverage: AgentRunCoverage | undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Convenience wrapper over {@link agentLoop} that exposes the run-level
|
|
34
|
+
* summary + coverage alongside the messages. The returned `stream` is the
|
|
35
|
+
* same `EventStream` callers already consume; `detailed()` awaits the
|
|
36
|
+
* stream's `agent_end` event and returns the additive fields.
|
|
37
|
+
*
|
|
38
|
+
* Existing `stream.result()` semantics are preserved — it still resolves to
|
|
39
|
+
* `AgentMessage[]`. Use {@link agentLoopDetailed} when you need the rollup;
|
|
40
|
+
* use {@link agentLoop} when you do not.
|
|
41
|
+
*/
|
|
42
|
+
export declare function agentLoopDetailed(prompts: AgentMessage[], context: AgentContext, config: AgentLoopConfig, signal?: AbortSignal, streamFn?: StreamFn): {
|
|
43
|
+
readonly stream: EventStream<AgentEvent, AgentMessage[]>;
|
|
44
|
+
readonly detailed: () => Promise<AgentLoopDetailedResult>;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Like {@link agentLoopDetailed} but built on top of
|
|
48
|
+
* {@link agentLoopContinue}.
|
|
49
|
+
*/
|
|
50
|
+
export declare function agentLoopContinueDetailed(context: AgentContext, config: AgentLoopConfig, signal?: AbortSignal, streamFn?: StreamFn): {
|
|
51
|
+
readonly stream: EventStream<AgentEvent, AgentMessage[]>;
|
|
52
|
+
readonly detailed: () => Promise<AgentLoopDetailedResult>;
|
|
53
|
+
};
|
|
54
|
+
export declare const INTENT_FIELD = "_i";
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/** Agent class that uses the agent-loop directly.
|
|
2
|
+
* No transport abstraction - calls streamSimple via the loop.
|
|
3
|
+
*/
|
|
4
|
+
import { type AssistantMessage, type AssistantMessageEvent, type CursorExecHandlers, type CursorToolResultHandler, type Effort, type ImageContent, type Message, type Model, type ProviderSessionState, type ServiceTier, type SimpleStreamOptions, type ThinkingBudgets, type ToolChoice } from "@oh-my-pi/pi-ai";
|
|
5
|
+
import type { HarmonyAuditEvent } from "./harmony-leak";
|
|
6
|
+
import type { AgentEvent, AgentLoopConfig, AgentMessage, AgentState, AgentTool, AgentToolContext, StreamFn, ToolCallContext } from "./types";
|
|
7
|
+
export declare class AgentBusyError extends Error {
|
|
8
|
+
constructor(message?: string);
|
|
9
|
+
}
|
|
10
|
+
export interface AgentOptions {
|
|
11
|
+
initialState?: Partial<AgentState>;
|
|
12
|
+
/**
|
|
13
|
+
* Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.
|
|
14
|
+
* Default filters to user/assistant/toolResult and converts attachments.
|
|
15
|
+
*/
|
|
16
|
+
convertToLlm?: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Optional transform applied to context before convertToLlm.
|
|
19
|
+
* Use for context pruning, injecting external context, etc.
|
|
20
|
+
*/
|
|
21
|
+
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Steering mode: "all" = send all steering messages at once, "one-at-a-time" = one per turn
|
|
24
|
+
*/
|
|
25
|
+
steeringMode?: "all" | "one-at-a-time";
|
|
26
|
+
/**
|
|
27
|
+
* Follow-up mode: "all" = send all follow-up messages at once, "one-at-a-time" = one per turn
|
|
28
|
+
*/
|
|
29
|
+
followUpMode?: "all" | "one-at-a-time";
|
|
30
|
+
/**
|
|
31
|
+
* When to interrupt tool execution for steering messages.
|
|
32
|
+
* - "immediate": check after each tool call (default)
|
|
33
|
+
* - "wait": defer steering until the current turn completes
|
|
34
|
+
*/
|
|
35
|
+
interruptMode?: "immediate" | "wait";
|
|
36
|
+
/**
|
|
37
|
+
* API format for Kimi Code provider: "openai" or "anthropic" (default: "anthropic")
|
|
38
|
+
*/
|
|
39
|
+
kimiApiFormat?: "openai" | "anthropic";
|
|
40
|
+
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
|
|
41
|
+
preferWebsockets?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Custom stream function (for proxy backends, etc.). Default uses streamSimple.
|
|
44
|
+
*/
|
|
45
|
+
streamFn?: StreamFn;
|
|
46
|
+
/**
|
|
47
|
+
* Optional session identifier forwarded to LLM providers.
|
|
48
|
+
* Used by providers that support session-based caching (e.g., OpenAI Codex).
|
|
49
|
+
*/
|
|
50
|
+
sessionId?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Shared provider state map for session-scoped transport/session caches.
|
|
53
|
+
*/
|
|
54
|
+
providerSessionState?: Map<string, ProviderSessionState>;
|
|
55
|
+
/**
|
|
56
|
+
* Resolves an API key dynamically for each LLM call.
|
|
57
|
+
* Useful for expiring tokens (e.g., GitHub Copilot OAuth).
|
|
58
|
+
*/
|
|
59
|
+
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Inspect or replace provider payloads before they are sent.
|
|
62
|
+
*/
|
|
63
|
+
onPayload?: SimpleStreamOptions["onPayload"];
|
|
64
|
+
/**
|
|
65
|
+
* Inspect provider response metadata after headers arrive and before streaming body consumption.
|
|
66
|
+
*/
|
|
67
|
+
onResponse?: SimpleStreamOptions["onResponse"];
|
|
68
|
+
/**
|
|
69
|
+
* Inspect raw Server-Sent Events from HTTP streaming providers.
|
|
70
|
+
*/
|
|
71
|
+
onSseEvent?: SimpleStreamOptions["onSseEvent"];
|
|
72
|
+
/**
|
|
73
|
+
* Inspect assistant streaming events before they are emitted to subscribers.
|
|
74
|
+
* Use this when abort decisions must happen before buffered events continue flowing.
|
|
75
|
+
*/
|
|
76
|
+
onAssistantMessageEvent?: (message: AssistantMessage, event: AssistantMessageEvent) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Called when GPT-5 Harmony protocol leakage is detected and mitigated.
|
|
79
|
+
*/
|
|
80
|
+
onHarmonyLeak?: (event: HarmonyAuditEvent) => void | Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Custom token budgets for thinking levels (token-based providers only).
|
|
83
|
+
*/
|
|
84
|
+
thinkingBudgets?: ThinkingBudgets;
|
|
85
|
+
/**
|
|
86
|
+
* Sampling temperature for LLM calls. `undefined` uses provider default.
|
|
87
|
+
*/
|
|
88
|
+
temperature?: number;
|
|
89
|
+
/** Additional sampling controls for providers that support them. */
|
|
90
|
+
topP?: number;
|
|
91
|
+
topK?: number;
|
|
92
|
+
minP?: number;
|
|
93
|
+
presencePenalty?: number;
|
|
94
|
+
repetitionPenalty?: number;
|
|
95
|
+
serviceTier?: ServiceTier;
|
|
96
|
+
/**
|
|
97
|
+
* If true, request that the underlying provider omit reasoning/thinking summaries
|
|
98
|
+
* from the response. The model still reasons internally; only the human-readable
|
|
99
|
+
* summary stream is suppressed. Useful when the UI hides thinking blocks anyway.
|
|
100
|
+
*/
|
|
101
|
+
hideThinkingSummary?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Maximum delay in milliseconds to wait for a retry when the server requests a long wait.
|
|
104
|
+
* If the server's requested delay exceeds this value, the request fails immediately,
|
|
105
|
+
* allowing higher-level retry logic to handle it with user visibility.
|
|
106
|
+
* Default: 60000 (60 seconds). Set to 0 to disable the cap.
|
|
107
|
+
*/
|
|
108
|
+
maxRetryDelayMs?: number;
|
|
109
|
+
/**
|
|
110
|
+
* Provides tool execution context, resolved per tool call.
|
|
111
|
+
* Use for late-bound UI or session state access.
|
|
112
|
+
*/
|
|
113
|
+
getToolContext?: (toolCall?: ToolCallContext) => AgentToolContext | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Optional transform applied to tool call arguments before execution.
|
|
116
|
+
* Use for deobfuscating secrets or rewriting arguments.
|
|
117
|
+
*/
|
|
118
|
+
transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
|
|
119
|
+
/** Enable intent tracing schema injection/stripping in the harness. */
|
|
120
|
+
intentTracing?: boolean;
|
|
121
|
+
/** Dynamic tool choice override, resolved per LLM call. */
|
|
122
|
+
getToolChoice?: () => ToolChoice | undefined;
|
|
123
|
+
/**
|
|
124
|
+
* Cursor exec handlers for local tool execution.
|
|
125
|
+
*/
|
|
126
|
+
cursorExecHandlers?: CursorExecHandlers;
|
|
127
|
+
/**
|
|
128
|
+
* Cursor tool result callback for exec tool responses.
|
|
129
|
+
*/
|
|
130
|
+
cursorOnToolResult?: CursorToolResultHandler;
|
|
131
|
+
/**
|
|
132
|
+
* Called after a tool call has been validated and is about to execute.
|
|
133
|
+
* See {@link AgentLoopConfig.beforeToolCall} for full semantics.
|
|
134
|
+
*/
|
|
135
|
+
beforeToolCall?: AgentLoopConfig["beforeToolCall"];
|
|
136
|
+
/**
|
|
137
|
+
* Called after a tool finishes executing, before `tool_execution_end` and the tool-result
|
|
138
|
+
* message are emitted. See {@link AgentLoopConfig.afterToolCall} for full semantics.
|
|
139
|
+
*/
|
|
140
|
+
afterToolCall?: AgentLoopConfig["afterToolCall"];
|
|
141
|
+
/**
|
|
142
|
+
* Opt-in OpenTelemetry instrumentation. Passing `{}` enables the loop's
|
|
143
|
+
* GenAI-semantic-convention spans using the global tracer provider. See
|
|
144
|
+
* {@link AgentLoopConfig.telemetry} for the full surface.
|
|
145
|
+
*/
|
|
146
|
+
telemetry?: AgentLoopConfig["telemetry"];
|
|
147
|
+
}
|
|
148
|
+
export interface AgentPromptOptions {
|
|
149
|
+
toolChoice?: ToolChoice;
|
|
150
|
+
}
|
|
151
|
+
export declare class Agent {
|
|
152
|
+
#private;
|
|
153
|
+
streamFn: StreamFn;
|
|
154
|
+
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
|
155
|
+
/**
|
|
156
|
+
* Hook invoked after tool arguments are validated and before execution.
|
|
157
|
+
* Reassign at any time to swap the implementation (e.g. on extension reload).
|
|
158
|
+
*/
|
|
159
|
+
beforeToolCall?: AgentLoopConfig["beforeToolCall"];
|
|
160
|
+
/**
|
|
161
|
+
* Hook invoked after tool execution and before `tool_execution_end` / tool-result
|
|
162
|
+
* message emission. Reassign at any time to swap the implementation.
|
|
163
|
+
*/
|
|
164
|
+
afterToolCall?: AgentLoopConfig["afterToolCall"];
|
|
165
|
+
constructor(opts?: AgentOptions);
|
|
166
|
+
/**
|
|
167
|
+
* Get the current session ID used for provider caching.
|
|
168
|
+
*/
|
|
169
|
+
get sessionId(): string | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Set the session ID for provider caching.
|
|
172
|
+
* Call this when switching sessions (new session, branch, resume).
|
|
173
|
+
*/
|
|
174
|
+
set sessionId(value: string | undefined);
|
|
175
|
+
/**
|
|
176
|
+
* Static metadata forwarded to every API request when no resolver is installed
|
|
177
|
+
* (e.g. `metadata.user_id` for Anthropic session attribution). Setting this
|
|
178
|
+
* clears any installed resolver.
|
|
179
|
+
*
|
|
180
|
+
* For live/provider-aware metadata (e.g. Anthropic OAuth `account_uuid` that
|
|
181
|
+
* must reflect the credential selected per-request), use
|
|
182
|
+
* {@link setMetadataResolver} and read via {@link metadataForProvider}.
|
|
183
|
+
*/
|
|
184
|
+
get metadata(): Record<string, unknown> | undefined;
|
|
185
|
+
set metadata(value: Record<string, unknown> | undefined);
|
|
186
|
+
/**
|
|
187
|
+
* Resolve request metadata for the given provider at call time. When a
|
|
188
|
+
* resolver is installed via {@link setMetadataResolver}, it is invoked with
|
|
189
|
+
* the provider string so the result can be scoped (e.g. `account_uuid` is
|
|
190
|
+
* only included for `"anthropic"` requests). Falls back to the static
|
|
191
|
+
* {@link metadata} value when no resolver is set.
|
|
192
|
+
*/
|
|
193
|
+
metadataForProvider(provider: string): Record<string, unknown> | undefined;
|
|
194
|
+
/**
|
|
195
|
+
* Install a function that resolves request metadata at call time. The
|
|
196
|
+
* resolver receives the target provider string and can gate provider-specific
|
|
197
|
+
* fields (e.g. `account_uuid` only for `"anthropic"`). Invoked per LLM
|
|
198
|
+
* request by `agent-loop` after `getApiKey` selects the session-sticky
|
|
199
|
+
* credential. Pass `undefined` to clear and revert to the static
|
|
200
|
+
* {@link metadata} value.
|
|
201
|
+
*/
|
|
202
|
+
setMetadataResolver(resolver: ((provider: string) => Record<string, unknown> | undefined) | undefined): void;
|
|
203
|
+
/**
|
|
204
|
+
* Read the active OpenTelemetry configuration. Returns `undefined` when
|
|
205
|
+
* instrumentation is disabled. Callers spawning child runs (e.g. subagent
|
|
206
|
+
* dispatch) forward this to the child's loop so its spans appear under the
|
|
207
|
+
* parent's active context with the subagent's own identity stamped.
|
|
208
|
+
*/
|
|
209
|
+
get telemetry(): AgentLoopConfig["telemetry"] | undefined;
|
|
210
|
+
/**
|
|
211
|
+
* Replace the active OpenTelemetry configuration. Pass `undefined` to
|
|
212
|
+
* disable instrumentation. Applies to the *next* `agentLoop` invocation —
|
|
213
|
+
* in-flight loops keep the configuration they started with.
|
|
214
|
+
*/
|
|
215
|
+
setTelemetry(telemetry: AgentLoopConfig["telemetry"] | undefined): void;
|
|
216
|
+
/**
|
|
217
|
+
* Get provider-scoped mutable session state store.
|
|
218
|
+
*/
|
|
219
|
+
get providerSessionState(): Map<string, ProviderSessionState> | undefined;
|
|
220
|
+
/**
|
|
221
|
+
* Set provider-scoped mutable session state store.
|
|
222
|
+
*/
|
|
223
|
+
set providerSessionState(value: Map<string, ProviderSessionState> | undefined);
|
|
224
|
+
/**
|
|
225
|
+
* Get the current thinking budgets.
|
|
226
|
+
*/
|
|
227
|
+
get thinkingBudgets(): ThinkingBudgets | undefined;
|
|
228
|
+
/**
|
|
229
|
+
* Set custom thinking budgets for token-based providers.
|
|
230
|
+
*/
|
|
231
|
+
set thinkingBudgets(value: ThinkingBudgets | undefined);
|
|
232
|
+
/**
|
|
233
|
+
* Get the current sampling temperature.
|
|
234
|
+
*/
|
|
235
|
+
get temperature(): number | undefined;
|
|
236
|
+
/**
|
|
237
|
+
* Set sampling temperature for LLM calls. `undefined` uses provider default.
|
|
238
|
+
*/
|
|
239
|
+
set temperature(value: number | undefined);
|
|
240
|
+
get topP(): number | undefined;
|
|
241
|
+
set topP(value: number | undefined);
|
|
242
|
+
get topK(): number | undefined;
|
|
243
|
+
set topK(value: number | undefined);
|
|
244
|
+
get minP(): number | undefined;
|
|
245
|
+
set minP(value: number | undefined);
|
|
246
|
+
get presencePenalty(): number | undefined;
|
|
247
|
+
set presencePenalty(value: number | undefined);
|
|
248
|
+
get repetitionPenalty(): number | undefined;
|
|
249
|
+
set repetitionPenalty(value: number | undefined);
|
|
250
|
+
get serviceTier(): ServiceTier | undefined;
|
|
251
|
+
set serviceTier(value: ServiceTier | undefined);
|
|
252
|
+
get hideThinkingSummary(): boolean | undefined;
|
|
253
|
+
set hideThinkingSummary(value: boolean | undefined);
|
|
254
|
+
/**
|
|
255
|
+
* Get the current max retry delay in milliseconds.
|
|
256
|
+
*/
|
|
257
|
+
get maxRetryDelayMs(): number | undefined;
|
|
258
|
+
/**
|
|
259
|
+
* Set the maximum delay to wait for server-requested retries.
|
|
260
|
+
* Set to 0 to disable the cap.
|
|
261
|
+
*/
|
|
262
|
+
set maxRetryDelayMs(value: number | undefined);
|
|
263
|
+
get state(): AgentState;
|
|
264
|
+
subscribe(fn: (e: AgentEvent) => void): () => void;
|
|
265
|
+
setProviderResponseInterceptor(fn: SimpleStreamOptions["onResponse"] | undefined): void;
|
|
266
|
+
setRawSseEventInterceptor(fn: SimpleStreamOptions["onSseEvent"] | undefined): void;
|
|
267
|
+
setAssistantMessageEventInterceptor(fn: ((message: AssistantMessage, event: AssistantMessageEvent) => void) | undefined): void;
|
|
268
|
+
emitExternalEvent(event: AgentEvent): void;
|
|
269
|
+
setSystemPrompt(v: string[]): void;
|
|
270
|
+
setModel(m: Model): void;
|
|
271
|
+
setThinkingLevel(l: Effort | undefined): void;
|
|
272
|
+
setSteeringMode(mode: "all" | "one-at-a-time"): void;
|
|
273
|
+
getSteeringMode(): "all" | "one-at-a-time";
|
|
274
|
+
setFollowUpMode(mode: "all" | "one-at-a-time"): void;
|
|
275
|
+
getFollowUpMode(): "all" | "one-at-a-time";
|
|
276
|
+
setInterruptMode(mode: "immediate" | "wait"): void;
|
|
277
|
+
getInterruptMode(): "immediate" | "wait";
|
|
278
|
+
setTools(t: AgentTool<any>[]): void;
|
|
279
|
+
replaceMessages(ms: AgentMessage[]): void;
|
|
280
|
+
appendMessage(m: AgentMessage): void;
|
|
281
|
+
popMessage(): AgentMessage | undefined;
|
|
282
|
+
/**
|
|
283
|
+
* Queue a steering message to interrupt the agent mid-run.
|
|
284
|
+
* Delivered after current tool execution, skips remaining tools.
|
|
285
|
+
*/
|
|
286
|
+
steer(m: AgentMessage): void;
|
|
287
|
+
/**
|
|
288
|
+
* Queue a follow-up message to be processed after the agent finishes.
|
|
289
|
+
* Delivered only when agent has no more tool calls or steering messages.
|
|
290
|
+
*/
|
|
291
|
+
followUp(m: AgentMessage): void;
|
|
292
|
+
clearSteeringQueue(): void;
|
|
293
|
+
clearFollowUpQueue(): void;
|
|
294
|
+
clearAllQueues(): void;
|
|
295
|
+
hasQueuedMessages(): boolean;
|
|
296
|
+
/**
|
|
297
|
+
* Remove and return the last steering message from the queue (LIFO).
|
|
298
|
+
* Used by dequeue keybinding.
|
|
299
|
+
*/
|
|
300
|
+
popLastSteer(): AgentMessage | undefined;
|
|
301
|
+
/**
|
|
302
|
+
* Remove and return the last follow-up message from the queue (LIFO).
|
|
303
|
+
* Used by dequeue keybinding.
|
|
304
|
+
*/
|
|
305
|
+
popLastFollowUp(): AgentMessage | undefined;
|
|
306
|
+
clearMessages(): void;
|
|
307
|
+
abort(): void;
|
|
308
|
+
waitForIdle(): Promise<void>;
|
|
309
|
+
reset(): void;
|
|
310
|
+
/** Send a prompt with an AgentMessage */
|
|
311
|
+
prompt(message: AgentMessage | AgentMessage[], options?: AgentPromptOptions): Promise<void>;
|
|
312
|
+
prompt(input: string, options?: AgentPromptOptions): Promise<void>;
|
|
313
|
+
prompt(input: string, images?: ImageContent[], options?: AgentPromptOptions): Promise<void>;
|
|
314
|
+
/**
|
|
315
|
+
* Continue from current context (used for retries and resuming queued messages).
|
|
316
|
+
*/
|
|
317
|
+
continue(): Promise<void>;
|
|
318
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { Model } from "@oh-my-pi/pi-ai";
|
|
8
|
+
import type { AgentMessage } from "../types";
|
|
9
|
+
import type { ReadonlySessionManager, SessionEntry } from "./entries";
|
|
10
|
+
import { type ConvertToLlm } from "./messages";
|
|
11
|
+
import { type FileOperations } from "./utils";
|
|
12
|
+
export interface BranchSummaryResult {
|
|
13
|
+
summary?: string;
|
|
14
|
+
readFiles?: string[];
|
|
15
|
+
modifiedFiles?: string[];
|
|
16
|
+
aborted?: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Details stored in BranchSummaryEntry.details for file tracking */
|
|
20
|
+
export interface BranchSummaryDetails {
|
|
21
|
+
readFiles: string[];
|
|
22
|
+
modifiedFiles: string[];
|
|
23
|
+
}
|
|
24
|
+
export type { FileOperations } from "./utils";
|
|
25
|
+
export interface BranchPreparation {
|
|
26
|
+
/** Messages extracted for summarization, in chronological order */
|
|
27
|
+
messages: AgentMessage[];
|
|
28
|
+
/** File operations extracted from tool calls */
|
|
29
|
+
fileOps: FileOperations;
|
|
30
|
+
/** Total estimated tokens in messages */
|
|
31
|
+
totalTokens: number;
|
|
32
|
+
}
|
|
33
|
+
export interface CollectEntriesResult {
|
|
34
|
+
/** Entries to summarize, in chronological order */
|
|
35
|
+
entries: SessionEntry[];
|
|
36
|
+
/** Common ancestor between old and new position, if any */
|
|
37
|
+
commonAncestorId: string | null;
|
|
38
|
+
}
|
|
39
|
+
export interface GenerateBranchSummaryOptions {
|
|
40
|
+
/** Model to use for summarization */
|
|
41
|
+
model: Model;
|
|
42
|
+
/** API key for the model */
|
|
43
|
+
apiKey: string;
|
|
44
|
+
/** Abort signal for cancellation */
|
|
45
|
+
signal: AbortSignal;
|
|
46
|
+
/** Optional custom instructions for summarization */
|
|
47
|
+
customInstructions?: string;
|
|
48
|
+
/** Tokens reserved for prompt + LLM response (default 16384) */
|
|
49
|
+
reserveTokens?: number;
|
|
50
|
+
/** Optional metadata forwarded to the underlying API request (e.g. user_id for session attribution). */
|
|
51
|
+
metadata?: Record<string, unknown>;
|
|
52
|
+
/** Convert app-specific messages before serializing the branch summary prompt. */
|
|
53
|
+
convertToLlm?: ConvertToLlm;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Collect entries that should be summarized when navigating from one position to another.
|
|
57
|
+
*
|
|
58
|
+
* Walks from oldLeafId back to the common ancestor with targetId, collecting entries
|
|
59
|
+
* along the way. Does NOT stop at compaction boundaries - those are included and their
|
|
60
|
+
* summaries become context.
|
|
61
|
+
*
|
|
62
|
+
* @param session - Session manager (read-only access)
|
|
63
|
+
* @param oldLeafId - Current position (where we're navigating from)
|
|
64
|
+
* @param targetId - Target position (where we're navigating to)
|
|
65
|
+
* @returns Entries to summarize and the common ancestor
|
|
66
|
+
*/
|
|
67
|
+
export declare function collectEntriesForBranchSummary(session: ReadonlySessionManager, oldLeafId: string | null, targetId: string): CollectEntriesResult;
|
|
68
|
+
/**
|
|
69
|
+
* Prepare entries for summarization with token budget.
|
|
70
|
+
*
|
|
71
|
+
* Walks entries from NEWEST to OLDEST, adding messages until we hit the token budget.
|
|
72
|
+
* This ensures we keep the most recent context when the branch is too long.
|
|
73
|
+
*
|
|
74
|
+
* Also collects file operations from:
|
|
75
|
+
* - Tool calls in assistant messages
|
|
76
|
+
* - Existing branch_summary entries' details (for cumulative tracking)
|
|
77
|
+
*
|
|
78
|
+
* @param entries - Entries in chronological order
|
|
79
|
+
* @param tokenBudget - Maximum tokens to include (0 = no limit)
|
|
80
|
+
*/
|
|
81
|
+
export declare function prepareBranchEntries(entries: SessionEntry[], tokenBudget?: number): BranchPreparation;
|
|
82
|
+
/**
|
|
83
|
+
* Generate a summary of abandoned branch entries.
|
|
84
|
+
*
|
|
85
|
+
* @param entries - Session entries to summarize (chronological order)
|
|
86
|
+
* @param options - Generation options
|
|
87
|
+
*/
|
|
88
|
+
export declare function generateBranchSummary(entries: SessionEntry[], options: GenerateBranchSummaryOptions): Promise<BranchSummaryResult>;
|
|
@@ -0,0 +1,153 @@
|
|
|
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 MessageAttribution, type Model, type Usage } from "@oh-my-pi/pi-ai";
|
|
8
|
+
import type { AgentMessage, AgentTool } from "../types";
|
|
9
|
+
import type { SessionEntry } from "./entries";
|
|
10
|
+
import { type ConvertToLlm } from "./messages";
|
|
11
|
+
import { type FileOperations } from "./utils";
|
|
12
|
+
/** Details stored in CompactionEntry.details for file tracking */
|
|
13
|
+
export interface CompactionDetails {
|
|
14
|
+
readFiles: string[];
|
|
15
|
+
modifiedFiles: string[];
|
|
16
|
+
}
|
|
17
|
+
/** Result from compact() - SessionManager adds uuid/parentUuid when saving */
|
|
18
|
+
export interface CompactionResult<T = unknown> {
|
|
19
|
+
summary: string;
|
|
20
|
+
/** Short PR-style summary for display purposes. */
|
|
21
|
+
shortSummary?: string;
|
|
22
|
+
firstKeptEntryId: string;
|
|
23
|
+
tokensBefore: number;
|
|
24
|
+
/** Hook-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
|
|
25
|
+
details?: T;
|
|
26
|
+
/** Hook-provided data to persist alongside compaction entry. */
|
|
27
|
+
preserveData?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface CompactionSettings {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
strategy?: "context-full" | "handoff" | "off";
|
|
32
|
+
thresholdPercent?: number;
|
|
33
|
+
thresholdTokens?: number;
|
|
34
|
+
reserveTokens: number;
|
|
35
|
+
keepRecentTokens: number;
|
|
36
|
+
autoContinue?: boolean;
|
|
37
|
+
remoteEnabled?: boolean;
|
|
38
|
+
remoteEndpoint?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
|
|
41
|
+
/**
|
|
42
|
+
* Calculate total context tokens from usage.
|
|
43
|
+
* Uses the native totalTokens field when available, falls back to computing from components.
|
|
44
|
+
*/
|
|
45
|
+
export declare function calculateContextTokens(usage: Usage): number;
|
|
46
|
+
export declare function calculatePromptTokens(usage: Usage): number;
|
|
47
|
+
/**
|
|
48
|
+
* Find the last non-aborted assistant message usage from session entries.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Effective reserve: at least 15% of context window or the configured floor, whichever is larger.
|
|
53
|
+
*/
|
|
54
|
+
export declare function effectiveReserveTokens(contextWindow: number, settings: CompactionSettings): number;
|
|
55
|
+
/**
|
|
56
|
+
* Check if compaction should trigger based on context usage.
|
|
57
|
+
*/
|
|
58
|
+
export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean;
|
|
59
|
+
export declare function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings): number;
|
|
60
|
+
/**
|
|
61
|
+
* Estimate token count for a message using cl100k_base via the native
|
|
62
|
+
* tokenizer. This is not Claude's first-party tokenizer (Anthropic doesn't
|
|
63
|
+
* publish one) but is within ~5–10% across English/code text.
|
|
64
|
+
*/
|
|
65
|
+
export declare function estimateTokens(message: AgentMessage): number;
|
|
66
|
+
/**
|
|
67
|
+
* Find the user message (or bashExecution) that starts the turn containing the given entry index.
|
|
68
|
+
* Returns -1 if no turn start found before the index.
|
|
69
|
+
* BashExecutionMessage is treated like a user message for turn boundaries.
|
|
70
|
+
*/
|
|
71
|
+
export declare function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number;
|
|
72
|
+
export interface CutPointResult {
|
|
73
|
+
/** Index of first entry to keep */
|
|
74
|
+
firstKeptEntryIndex: number;
|
|
75
|
+
/** Index of user message that starts the turn being split, or -1 if not splitting */
|
|
76
|
+
turnStartIndex: number;
|
|
77
|
+
/** Whether this cut splits a turn (cut point is not a user message) */
|
|
78
|
+
isSplitTurn: boolean;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Find the cut point in session entries that keeps approximately `keepRecentTokens`.
|
|
82
|
+
*
|
|
83
|
+
* Algorithm: Walk backwards from newest, accumulating estimated message sizes.
|
|
84
|
+
* Stop when we've accumulated >= keepRecentTokens. Cut at that point.
|
|
85
|
+
*
|
|
86
|
+
* Can cut at user OR assistant messages (never tool results). When cutting at an
|
|
87
|
+
* assistant message with tool calls, its tool results come after and will be kept.
|
|
88
|
+
*
|
|
89
|
+
* Returns CutPointResult with:
|
|
90
|
+
* - firstKeptEntryIndex: the entry index to start keeping from
|
|
91
|
+
* - turnStartIndex: if cutting mid-turn, the user message that started that turn
|
|
92
|
+
* - isSplitTurn: whether we're cutting in the middle of a turn
|
|
93
|
+
*
|
|
94
|
+
* Only considers entries between `startIndex` and `endIndex` (exclusive).
|
|
95
|
+
*/
|
|
96
|
+
export declare function findCutPoint(entries: SessionEntry[], startIndex: number, endIndex: number, keepRecentTokens: number): CutPointResult;
|
|
97
|
+
export declare const AUTO_HANDOFF_THRESHOLD_FOCUS: string;
|
|
98
|
+
/**
|
|
99
|
+
* Generate a summary of the conversation using the LLM.
|
|
100
|
+
* If previousSummary is provided, uses the update prompt to merge.
|
|
101
|
+
*/
|
|
102
|
+
export interface SummaryOptions {
|
|
103
|
+
promptOverride?: string;
|
|
104
|
+
extraContext?: string[];
|
|
105
|
+
remoteEndpoint?: string;
|
|
106
|
+
remoteInstructions?: string;
|
|
107
|
+
initiatorOverride?: MessageAttribution;
|
|
108
|
+
metadata?: Record<string, unknown>;
|
|
109
|
+
convertToLlm?: ConvertToLlm;
|
|
110
|
+
}
|
|
111
|
+
export declare function generateSummary(currentMessages: AgentMessage[], model: Model, reserveTokens: number, apiKey: string, signal?: AbortSignal, customInstructions?: string, previousSummary?: string, options?: SummaryOptions): Promise<string>;
|
|
112
|
+
export interface HandoffOptions {
|
|
113
|
+
/** Live agent system prompt — passed verbatim so providers hit the cached prefix. */
|
|
114
|
+
systemPrompt: string[];
|
|
115
|
+
/** Live agent tool list — same purpose. Forced to `toolChoice: "none"`. */
|
|
116
|
+
tools?: AgentTool<any>[];
|
|
117
|
+
customInstructions?: string;
|
|
118
|
+
convertToLlm?: ConvertToLlm;
|
|
119
|
+
initiatorOverride?: MessageAttribution;
|
|
120
|
+
metadata?: Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
export declare function renderHandoffPrompt(customInstructions?: string): string;
|
|
123
|
+
export declare function generateHandoff(messages: AgentMessage[], model: Model, apiKey: string, options: HandoffOptions, signal?: AbortSignal): Promise<string>;
|
|
124
|
+
export interface CompactionPreparation {
|
|
125
|
+
/** UUID of first entry to keep */
|
|
126
|
+
firstKeptEntryId: string;
|
|
127
|
+
/** Messages that will be summarized and discarded */
|
|
128
|
+
messagesToSummarize: AgentMessage[];
|
|
129
|
+
/** Messages that will be turned into turn prefix summary (if splitting) */
|
|
130
|
+
turnPrefixMessages: AgentMessage[];
|
|
131
|
+
/** Messages kept in full after compaction (recent history) */
|
|
132
|
+
recentMessages: AgentMessage[];
|
|
133
|
+
/** Whether this is a split turn (cut point in middle of turn) */
|
|
134
|
+
isSplitTurn: boolean;
|
|
135
|
+
tokensBefore: number;
|
|
136
|
+
/** Summary from previous compaction, for iterative update */
|
|
137
|
+
previousSummary?: string;
|
|
138
|
+
/** Preserved opaque compaction payload from the previous compaction, if any. */
|
|
139
|
+
previousPreserveData?: Record<string, unknown>;
|
|
140
|
+
/** File operations extracted from messagesToSummarize */
|
|
141
|
+
fileOps: FileOperations;
|
|
142
|
+
/** Compaction settions from settings.jsonl */
|
|
143
|
+
settings: CompactionSettings;
|
|
144
|
+
}
|
|
145
|
+
export declare function prepareCompaction(pathEntries: SessionEntry[], settings: CompactionSettings): CompactionPreparation | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* Generate summaries for compaction using prepared data.
|
|
148
|
+
* Returns CompactionResult - SessionManager adds id/parentId when saving.
|
|
149
|
+
*
|
|
150
|
+
* @param preparation - Pre-calculated preparation from prepareCompaction()
|
|
151
|
+
* @param customInstructions - Optional custom focus for the summary
|
|
152
|
+
*/
|
|
153
|
+
export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: string, customInstructions?: string, signal?: AbortSignal, options?: SummaryOptions): Promise<CompactionResult>;
|