@astralyn/pi 0.84.11 → 0.84.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 +31 -0
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +2 -1
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/core/agent-session.d.ts +62 -8
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +395 -78
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +4 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +5 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +19 -5
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +44 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +18 -10
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +4 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +8 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +17 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts +5 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +5 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/extensions/background/actions.d.ts +21 -0
- package/dist/extensions/background/actions.d.ts.map +1 -0
- package/dist/extensions/background/actions.js +264 -0
- package/dist/extensions/background/actions.js.map +1 -0
- package/dist/extensions/background/constants.d.ts +24 -0
- package/dist/extensions/background/constants.d.ts.map +1 -0
- package/dist/extensions/background/constants.js +24 -0
- package/dist/extensions/background/constants.js.map +1 -0
- package/dist/extensions/background/index.d.ts +6 -115
- package/dist/extensions/background/index.d.ts.map +1 -1
- package/dist/extensions/background/index.js +38 -509
- package/dist/extensions/background/index.js.map +1 -1
- package/dist/extensions/background/manager.d.ts +2 -1
- package/dist/extensions/background/manager.d.ts.map +1 -1
- package/dist/extensions/background/manager.js +8 -10
- package/dist/extensions/background/manager.js.map +1 -1
- package/dist/extensions/background/notify.d.ts +16 -0
- package/dist/extensions/background/notify.d.ts.map +1 -0
- package/dist/extensions/background/notify.js +114 -0
- package/dist/extensions/background/notify.js.map +1 -0
- package/dist/extensions/background/output-file.d.ts +58 -0
- package/dist/extensions/background/output-file.d.ts.map +1 -0
- package/dist/extensions/background/output-file.js +122 -0
- package/dist/extensions/background/output-file.js.map +1 -0
- package/dist/extensions/background/registry.d.ts +40 -72
- package/dist/extensions/background/registry.d.ts.map +1 -1
- package/dist/extensions/background/registry.js +71 -184
- package/dist/extensions/background/registry.js.map +1 -1
- package/dist/extensions/background/render.d.ts +3 -10
- package/dist/extensions/background/render.d.ts.map +1 -1
- package/dist/extensions/background/render.js +20 -60
- package/dist/extensions/background/render.js.map +1 -1
- package/dist/extensions/background/schema.d.ts +34 -0
- package/dist/extensions/background/schema.d.ts.map +1 -0
- package/dist/extensions/background/schema.js +89 -0
- package/dist/extensions/background/schema.js.map +1 -0
- package/dist/extensions/background/task-view.d.ts +48 -0
- package/dist/extensions/background/task-view.d.ts.map +1 -0
- package/dist/extensions/background/task-view.js +90 -0
- package/dist/extensions/background/task-view.js.map +1 -0
- package/dist/extensions/background/text.d.ts +15 -0
- package/dist/extensions/background/text.d.ts.map +1 -0
- package/dist/extensions/background/text.js +31 -0
- package/dist/extensions/background/text.js.map +1 -0
- package/dist/extensions/background/types.d.ts +74 -0
- package/dist/extensions/background/types.d.ts.map +1 -0
- package/dist/extensions/background/types.js +17 -0
- package/dist/extensions/background/types.js.map +1 -0
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +2 -0
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/llama/client.d.ts +6 -0
- package/dist/extensions/llama/client.d.ts.map +1 -1
- package/dist/extensions/llama/client.js +7 -0
- package/dist/extensions/llama/client.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts +3 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +27 -6
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/extensions/subagent/activity.d.ts +5 -0
- package/dist/extensions/subagent/activity.d.ts.map +1 -1
- package/dist/extensions/subagent/activity.js +5 -0
- package/dist/extensions/subagent/activity.js.map +1 -1
- package/dist/extensions/subagent/agents-command.d.ts.map +1 -1
- package/dist/extensions/subagent/agents-command.js +6 -391
- package/dist/extensions/subagent/agents-command.js.map +1 -1
- package/dist/extensions/subagent/constants.d.ts.map +1 -1
- package/dist/extensions/subagent/constants.js +1 -0
- package/dist/extensions/subagent/constants.js.map +1 -1
- package/dist/extensions/subagent/render.d.ts.map +1 -1
- package/dist/extensions/subagent/render.js +15 -1
- package/dist/extensions/subagent/render.js.map +1 -1
- package/dist/extensions/subagent/resolve.d.ts.map +1 -1
- package/dist/extensions/subagent/resolve.js +2 -4
- package/dist/extensions/subagent/resolve.js.map +1 -1
- package/dist/extensions/subagent/runner.d.ts +1 -1
- package/dist/extensions/subagent/runner.d.ts.map +1 -1
- package/dist/extensions/subagent/runner.js +8 -8
- package/dist/extensions/subagent/runner.js.map +1 -1
- package/dist/extensions/subagent/sdk-runner.d.ts +11 -0
- package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
- package/dist/extensions/subagent/sdk-runner.js +90 -79
- package/dist/extensions/subagent/sdk-runner.js.map +1 -1
- package/dist/extensions/subagent/state.d.ts.map +1 -1
- package/dist/extensions/subagent/state.js +145 -126
- package/dist/extensions/subagent/state.js.map +1 -1
- package/dist/extensions/subagent/text.d.ts.map +1 -1
- package/dist/extensions/subagent/text.js +4 -0
- package/dist/extensions/subagent/text.js.map +1 -1
- package/dist/extensions/subagent/ui/agents-dialog.d.ts +4 -0
- package/dist/extensions/subagent/ui/agents-dialog.d.ts.map +1 -0
- package/dist/extensions/subagent/ui/agents-dialog.js +84 -0
- package/dist/extensions/subagent/ui/agents-dialog.js.map +1 -0
- package/dist/extensions/subagent/ui/choice-menu.d.ts +39 -0
- package/dist/extensions/subagent/ui/choice-menu.d.ts.map +1 -0
- package/dist/extensions/subagent/ui/choice-menu.js +93 -0
- package/dist/extensions/subagent/ui/choice-menu.js.map +1 -0
- package/dist/extensions/subagent/ui/model-picker.d.ts +47 -0
- package/dist/extensions/subagent/ui/model-picker.d.ts.map +1 -0
- package/dist/extensions/subagent/ui/model-picker.js +142 -0
- package/dist/extensions/subagent/ui/model-picker.js.map +1 -0
- package/dist/extensions/subagent/ui/profile-settings-menu.d.ts +41 -0
- package/dist/extensions/subagent/ui/profile-settings-menu.d.ts.map +1 -0
- package/dist/extensions/subagent/ui/profile-settings-menu.js +98 -0
- package/dist/extensions/subagent/ui/profile-settings-menu.js.map +1 -0
- package/dist/extensions/web-search/auth.d.ts +12 -0
- package/dist/extensions/web-search/auth.d.ts.map +1 -0
- package/dist/extensions/web-search/auth.js +90 -0
- package/dist/extensions/web-search/auth.js.map +1 -0
- package/dist/extensions/web-search/constants.d.ts +13 -0
- package/dist/extensions/web-search/constants.d.ts.map +1 -0
- package/dist/extensions/web-search/constants.js +27 -0
- package/dist/extensions/web-search/constants.js.map +1 -0
- package/dist/extensions/web-search/fusion.d.ts +36 -0
- package/dist/extensions/web-search/fusion.d.ts.map +1 -0
- package/dist/extensions/web-search/fusion.js +272 -0
- package/dist/extensions/web-search/fusion.js.map +1 -0
- package/dist/extensions/web-search/index.d.ts +6 -0
- package/dist/extensions/web-search/index.d.ts.map +1 -0
- package/dist/extensions/web-search/index.js +70 -0
- package/dist/extensions/web-search/index.js.map +1 -0
- package/dist/extensions/web-search/providers/deepseek.d.ts +15 -0
- package/dist/extensions/web-search/providers/deepseek.d.ts.map +1 -0
- package/dist/extensions/web-search/providers/deepseek.js +63 -0
- package/dist/extensions/web-search/providers/deepseek.js.map +1 -0
- package/dist/extensions/web-search/providers/http.d.ts +9 -0
- package/dist/extensions/web-search/providers/http.d.ts.map +1 -0
- package/dist/extensions/web-search/providers/http.js +25 -0
- package/dist/extensions/web-search/providers/http.js.map +1 -0
- package/dist/extensions/web-search/providers/minimax.d.ts +14 -0
- package/dist/extensions/web-search/providers/minimax.d.ts.map +1 -0
- package/dist/extensions/web-search/providers/minimax.js +69 -0
- package/dist/extensions/web-search/providers/minimax.js.map +1 -0
- package/dist/extensions/web-search/render.d.ts +11 -0
- package/dist/extensions/web-search/render.d.ts.map +1 -0
- package/dist/extensions/web-search/render.js +109 -0
- package/dist/extensions/web-search/render.js.map +1 -0
- package/dist/extensions/web-search/schema.d.ts +12 -0
- package/dist/extensions/web-search/schema.d.ts.map +1 -0
- package/dist/extensions/web-search/schema.js +57 -0
- package/dist/extensions/web-search/schema.js.map +1 -0
- package/dist/extensions/web-search/types.d.ts +111 -0
- package/dist/extensions/web-search/types.d.ts.map +1 -0
- package/dist/extensions/web-search/types.js +5 -0
- package/dist/extensions/web-search/types.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +71 -58
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +7 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +3 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +12 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +6 -4
- package/dist/utils/shell.js.map +1 -1
- package/docs/bundled/README.md +1 -0
- package/docs/bundled/extensions/web-search.md +50 -0
- package/docs/compaction.md +5 -12
- package/docs/environment-variables.md +3 -0
- package/docs/extensions.md +21 -5
- package/docs/keybindings.md +1 -1
- package/docs/quickstart.md +1 -1
- package/docs/rpc.md +23 -0
- package/docs/settings.md +7 -0
- package/docs/terminal-setup.md +12 -0
- package/docs/usage.md +4 -3
- package/npm-shrinkwrap.json +29 -29
- package/package.json +6 -6
- package/dist/core/compaction-controller.d.ts +0 -90
- package/dist/core/compaction-controller.d.ts.map +0 -1
- package/dist/core/compaction-controller.js +0 -471
- package/dist/core/compaction-controller.js.map +0 -1
|
@@ -15,15 +15,14 @@
|
|
|
15
15
|
import { readFileSync } from "node:fs";
|
|
16
16
|
import { basename, dirname } from "node:path";
|
|
17
17
|
import { contentText } from "@earendil-works/pi-ai";
|
|
18
|
-
import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
|
|
18
|
+
import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isRecoverableLength, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
|
|
19
19
|
import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
|
|
20
20
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
21
21
|
import { sleep } from "../utils/sleep.js";
|
|
22
22
|
import { normalizeToolResultImages } from "../utils/tool-result-images.js";
|
|
23
23
|
import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
|
|
24
24
|
import { executeBashWithOperations } from "./bash-executor.js";
|
|
25
|
-
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
26
|
-
import { CONTEXT_REMAINS_OVER_COMPACTION_THRESHOLD, CompactionController, estimateMessagesTokens, } from "./compaction-controller.js";
|
|
25
|
+
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
27
26
|
import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "./defaults.js";
|
|
28
27
|
import { exportSessionToHtml } from "./export-html/index.js";
|
|
29
28
|
import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
@@ -62,6 +61,13 @@ function withoutDeletedHeaders(headers) {
|
|
|
62
61
|
? Object.fromEntries(Object.entries(headers).filter((entry) => entry[1] !== null))
|
|
63
62
|
: undefined;
|
|
64
63
|
}
|
|
64
|
+
function estimateMessagesTokens(messages) {
|
|
65
|
+
let tokens = 0;
|
|
66
|
+
for (const message of messages) {
|
|
67
|
+
tokens += estimateTokens(message);
|
|
68
|
+
}
|
|
69
|
+
return tokens;
|
|
70
|
+
}
|
|
65
71
|
// ============================================================================
|
|
66
72
|
// Constants
|
|
67
73
|
// ============================================================================
|
|
@@ -78,8 +84,12 @@ export class AgentSession {
|
|
|
78
84
|
this._followUpMessages = [];
|
|
79
85
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
80
86
|
this._pendingNextTurnMessages = [];
|
|
87
|
+
/** Context-only custom messages queued during a run, flushed once the current turn's tool results are in. */
|
|
88
|
+
this._pendingCustomMessages = [];
|
|
81
89
|
// Compaction state
|
|
82
90
|
this._compactionAbortController = undefined;
|
|
91
|
+
this._autoCompactionAbortController = undefined;
|
|
92
|
+
this._overflowRecoveryAttempted = false;
|
|
83
93
|
// Branch summarization state
|
|
84
94
|
this._branchSummaryAbortController = undefined;
|
|
85
95
|
// Retry state
|
|
@@ -105,7 +115,7 @@ export class AgentSession {
|
|
|
105
115
|
// When a user message starts, check if it's from either queue and remove it BEFORE emitting
|
|
106
116
|
// This ensures the UI sees the updated queue state
|
|
107
117
|
if (event.type === "message_start" && event.message.role === "user") {
|
|
108
|
-
this.
|
|
118
|
+
this._overflowRecoveryAttempted = false;
|
|
109
119
|
const messageText = contentText(event.message.content, "");
|
|
110
120
|
if (messageText) {
|
|
111
121
|
// Check steering queue first
|
|
@@ -147,7 +157,7 @@ export class AgentSession {
|
|
|
147
157
|
this._lastAssistantMessage = event.message;
|
|
148
158
|
const assistantMsg = event.message;
|
|
149
159
|
if (assistantMsg.stopReason !== "error" && assistantMsg.stopReason !== "length") {
|
|
150
|
-
this.
|
|
160
|
+
this._overflowRecoveryAttempted = false;
|
|
151
161
|
}
|
|
152
162
|
// Reset retry counter immediately on successful assistant response
|
|
153
163
|
// This prevents accumulation across multiple LLM calls within a turn
|
|
@@ -161,6 +171,14 @@ export class AgentSession {
|
|
|
161
171
|
}
|
|
162
172
|
}
|
|
163
173
|
}
|
|
174
|
+
// A turn ends after its assistant message and every tool result has been appended,
|
|
175
|
+
// so this is the first point in the run where a context-only custom message can be
|
|
176
|
+
// inserted without landing between a tool call and its result. Flushing after the
|
|
177
|
+
// extension and listener dispatch above also picks up messages that turn_end
|
|
178
|
+
// handlers queued.
|
|
179
|
+
if (event.type === "turn_end") {
|
|
180
|
+
this._flushPendingCustomMessages();
|
|
181
|
+
}
|
|
164
182
|
};
|
|
165
183
|
this.agent = config.agent;
|
|
166
184
|
this.sessionManager = config.sessionManager;
|
|
@@ -176,20 +194,6 @@ export class AgentSession {
|
|
|
176
194
|
this._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : undefined;
|
|
177
195
|
this._baseToolsOverride = config.baseToolsOverride;
|
|
178
196
|
this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
|
|
179
|
-
this._compaction = new CompactionController({
|
|
180
|
-
agent: this.agent,
|
|
181
|
-
sessionManager: this.sessionManager,
|
|
182
|
-
settingsManager: this.settingsManager,
|
|
183
|
-
getModel: () => this.model,
|
|
184
|
-
getThinkingLevel: () => this.thinkingLevel,
|
|
185
|
-
getExtensionRunner: () => this._extensionRunner,
|
|
186
|
-
isManualCompactionActive: () => this._compactionAbortController !== undefined,
|
|
187
|
-
emit: (event) => this._emit(event),
|
|
188
|
-
emitSessionCompactFailed: (event) => this._emitSessionCompactFailed(event),
|
|
189
|
-
getRequiredRequestAuth: (model) => this._getRequiredRequestAuth(model),
|
|
190
|
-
getSummarizationRequestAuth: (model, streamFunction) => this._getSummarizationRequestAuth(model, streamFunction),
|
|
191
|
-
summarizationRetryCallbacks: (source) => this._summarizationRetryCallbacks(source),
|
|
192
|
-
});
|
|
193
197
|
// Always subscribe to agent events for internal handling
|
|
194
198
|
// (session persistence, extensions, auto-compaction, retry logic)
|
|
195
199
|
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
|
|
@@ -232,6 +236,14 @@ export class AgentSession {
|
|
|
232
236
|
}
|
|
233
237
|
throw new Error(formatNoApiKeyFoundMessage(model.provider));
|
|
234
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Resolve summarization auth against a caller-supplied stream function.
|
|
241
|
+
*
|
|
242
|
+
* Callers that await this while the session stays live must capture
|
|
243
|
+
* `this.agent.streamFunction` first and pass it in: a model switch during the
|
|
244
|
+
* await would otherwise select auth for one stream function and generate the
|
|
245
|
+
* summary with another.
|
|
246
|
+
*/
|
|
235
247
|
async _getSummarizationRequestAuth(model, streamFunction = this.agent.streamFunction) {
|
|
236
248
|
if (streamFunction === streamSimple) {
|
|
237
249
|
return this._getRequiredRequestAuth(model);
|
|
@@ -311,35 +323,33 @@ export class AgentSession {
|
|
|
311
323
|
};
|
|
312
324
|
};
|
|
313
325
|
}
|
|
326
|
+
async _compactBeforeNextAssistantResponse(context) {
|
|
327
|
+
const model = this.model;
|
|
328
|
+
const settings = this.settingsManager.getCompactionSettings();
|
|
329
|
+
if (!model ||
|
|
330
|
+
model.contextWindow <= 0 ||
|
|
331
|
+
!shouldCompact(estimateContextTokens(context.messages).tokens, model.contextWindow, settings)) {
|
|
332
|
+
return context;
|
|
333
|
+
}
|
|
334
|
+
await this._runAutoCompaction("threshold", false);
|
|
335
|
+
return {
|
|
336
|
+
...context,
|
|
337
|
+
messages: this.agent.state.messages.slice(),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
314
340
|
_installAgentNextTurnRefresh() {
|
|
315
341
|
const previousPrepareNextTurnWithContext = this.agent.prepareNextTurnWithContext ??
|
|
316
342
|
(this.agent.prepareNextTurn
|
|
317
343
|
? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
|
|
318
344
|
: undefined);
|
|
319
345
|
this.agent.prepareNextTurnWithContext = async (turn, signal) => {
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
// exits before queue polling or another provider request; the run records a
|
|
324
|
-
// normal error/aborted lifecycle instead of risking an oversized context.
|
|
325
|
-
this._compaction.requestStopAfterTurn();
|
|
326
|
-
throw new Error(compaction.stopErrorMessage);
|
|
327
|
-
}
|
|
328
|
-
const effectiveTurn = compaction.messages === undefined
|
|
329
|
-
? turn
|
|
330
|
-
: {
|
|
331
|
-
...turn,
|
|
332
|
-
context: {
|
|
333
|
-
...turn.context,
|
|
334
|
-
messages: compaction.messages,
|
|
335
|
-
},
|
|
336
|
-
};
|
|
337
|
-
const previousSnapshot = await previousPrepareNextTurnWithContext?.(effectiveTurn, signal);
|
|
338
|
-
const previousContext = previousSnapshot?.context ?? effectiveTurn.context;
|
|
346
|
+
const context = await this._compactBeforeNextAssistantResponse(turn.context);
|
|
347
|
+
const previousSnapshot = await previousPrepareNextTurnWithContext?.({ ...turn, context }, signal);
|
|
348
|
+
const nextContext = previousSnapshot?.context ?? context;
|
|
339
349
|
return {
|
|
340
350
|
...previousSnapshot,
|
|
341
351
|
context: {
|
|
342
|
-
...
|
|
352
|
+
...nextContext,
|
|
343
353
|
systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
|
|
344
354
|
tools: this.agent.state.tools.slice(),
|
|
345
355
|
},
|
|
@@ -643,7 +653,7 @@ export class AgentSession {
|
|
|
643
653
|
}
|
|
644
654
|
/** Whether compaction or branch summarization is currently running */
|
|
645
655
|
get isCompacting() {
|
|
646
|
-
return (this.
|
|
656
|
+
return (this._autoCompactionAbortController !== undefined ||
|
|
647
657
|
this._compactionAbortController !== undefined ||
|
|
648
658
|
this._branchSummaryAbortController !== undefined);
|
|
649
659
|
}
|
|
@@ -741,7 +751,6 @@ export class AgentSession {
|
|
|
741
751
|
// =========================================================================
|
|
742
752
|
async _runAgentPrompt(messages) {
|
|
743
753
|
this._isAgentRunActive = true;
|
|
744
|
-
this._compaction.noteRunStart();
|
|
745
754
|
try {
|
|
746
755
|
await this.agent.prompt(messages);
|
|
747
756
|
while (await this._handlePostAgentRun()) {
|
|
@@ -751,6 +760,7 @@ export class AgentSession {
|
|
|
751
760
|
finally {
|
|
752
761
|
this._systemPromptOverride = undefined;
|
|
753
762
|
this._flushPendingBashMessages();
|
|
763
|
+
this._flushPendingCustomMessages();
|
|
754
764
|
await this._emitAgentSettled();
|
|
755
765
|
}
|
|
756
766
|
}
|
|
@@ -758,10 +768,6 @@ export class AgentSession {
|
|
|
758
768
|
const msg = this._lastAssistantMessage;
|
|
759
769
|
this._lastAssistantMessage = undefined;
|
|
760
770
|
if (!msg) {
|
|
761
|
-
this._compaction.consumeStopAfterTurn();
|
|
762
|
-
return false;
|
|
763
|
-
}
|
|
764
|
-
if (this._compaction.consumeStopAfterTurn()) {
|
|
765
771
|
return false;
|
|
766
772
|
}
|
|
767
773
|
if (this._isRetryableError(msg) && (await this._prepareRetry(msg))) {
|
|
@@ -776,12 +782,9 @@ export class AgentSession {
|
|
|
776
782
|
});
|
|
777
783
|
this._retryAttempt = 0;
|
|
778
784
|
}
|
|
779
|
-
if (await this.
|
|
785
|
+
if (await this._checkCompaction(msg)) {
|
|
780
786
|
return true;
|
|
781
787
|
}
|
|
782
|
-
if (this._compaction.consumeStopAfterTurn()) {
|
|
783
|
-
return false;
|
|
784
|
-
}
|
|
785
788
|
// The agent loop drains both queues before emitting agent_end. Any messages
|
|
786
789
|
// here were queued by agent_end extension handlers and need a continuation.
|
|
787
790
|
return this.agent.hasQueuedMessages();
|
|
@@ -847,8 +850,9 @@ export class AgentSession {
|
|
|
847
850
|
preflightResult?.(true);
|
|
848
851
|
return;
|
|
849
852
|
}
|
|
850
|
-
// Flush any pending bash messages before the new prompt
|
|
853
|
+
// Flush any pending bash and custom messages before the new prompt
|
|
851
854
|
this._flushPendingBashMessages();
|
|
855
|
+
this._flushPendingCustomMessages();
|
|
852
856
|
// Validate model
|
|
853
857
|
if (!this.model) {
|
|
854
858
|
throw new Error(formatNoModelSelectedMessage());
|
|
@@ -868,14 +872,7 @@ export class AgentSession {
|
|
|
868
872
|
// The user's new prompt is sent below, so do not call agent.continue() here.
|
|
869
873
|
const lastAssistant = this._findLastAssistantMessage();
|
|
870
874
|
if (lastAssistant) {
|
|
871
|
-
await this.
|
|
872
|
-
}
|
|
873
|
-
const compactionSettings = this.settingsManager.getCompactionSettings();
|
|
874
|
-
// A compaction entry can be valid while its retained suffix is still too large.
|
|
875
|
-
// Refuse the existing context here rather than discovering that via provider overflow.
|
|
876
|
-
if (compactionSettings.enabled &&
|
|
877
|
-
shouldCompact(this._compaction.estimateContextTokens(this.agent.state.messages), this.model.contextWindow, compactionSettings)) {
|
|
878
|
-
throw new Error(CONTEXT_REMAINS_OVER_COMPACTION_THRESHOLD);
|
|
875
|
+
await this._checkCompaction(lastAssistant, false);
|
|
879
876
|
}
|
|
880
877
|
// Build messages array (custom message if any, then user message)
|
|
881
878
|
messages = [];
|
|
@@ -1069,8 +1066,9 @@ export class AgentSession {
|
|
|
1069
1066
|
/**
|
|
1070
1067
|
* Send a custom message to the session. Creates a CustomMessageEntry.
|
|
1071
1068
|
*
|
|
1072
|
-
* Handles
|
|
1069
|
+
* Handles four cases:
|
|
1073
1070
|
* - Streaming: queues message, processed when loop pulls from queue
|
|
1071
|
+
* - Streaming + triggerTurn false: appended to state/session once the current turn ends
|
|
1074
1072
|
* - Not streaming + triggerTurn: appends to state/session, starts new turn
|
|
1075
1073
|
* - Not streaming + no trigger: appends to state/session, no turn
|
|
1076
1074
|
*
|
|
@@ -1102,11 +1100,34 @@ export class AgentSession {
|
|
|
1102
1100
|
else if (options?.triggerTurn) {
|
|
1103
1101
|
await this._runAgentPrompt(appMessage);
|
|
1104
1102
|
}
|
|
1103
|
+
else if (this.isStreaming) {
|
|
1104
|
+
// Appending now would put the message between an assistant tool call and its
|
|
1105
|
+
// result, which providers that validate message order reject on replay. Defer
|
|
1106
|
+
// to the end of the turn. Nothing is emitted yet: message events must not
|
|
1107
|
+
// describe messages the session tree does not contain.
|
|
1108
|
+
this._pendingCustomMessages.push(appMessage);
|
|
1109
|
+
}
|
|
1105
1110
|
else {
|
|
1106
|
-
this.
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1111
|
+
this._appendCustomMessage(appMessage);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
_appendCustomMessage(appMessage) {
|
|
1115
|
+
this.agent.state.messages.push(appMessage);
|
|
1116
|
+
this.sessionManager.appendCustomMessageEntry(appMessage.customType, appMessage.content, appMessage.display, appMessage.details);
|
|
1117
|
+
this._emit({ type: "message_start", message: appMessage });
|
|
1118
|
+
this._emit({ type: "message_end", message: appMessage });
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Append custom messages queued while the agent was running.
|
|
1122
|
+
* Called once the current turn's tool results are in agent state and session history.
|
|
1123
|
+
*/
|
|
1124
|
+
_flushPendingCustomMessages() {
|
|
1125
|
+
if (this._pendingCustomMessages.length === 0)
|
|
1126
|
+
return;
|
|
1127
|
+
const pending = this._pendingCustomMessages;
|
|
1128
|
+
this._pendingCustomMessages = [];
|
|
1129
|
+
for (const appMessage of pending) {
|
|
1130
|
+
this._appendCustomMessage(appMessage);
|
|
1110
1131
|
}
|
|
1111
1132
|
}
|
|
1112
1133
|
/**
|
|
@@ -1139,7 +1160,6 @@ export class AgentSession {
|
|
|
1139
1160
|
if (images.length === 0)
|
|
1140
1161
|
images = undefined;
|
|
1141
1162
|
}
|
|
1142
|
-
// Use prompt() with the requested command and template expansion behavior
|
|
1143
1163
|
await this.prompt(text, {
|
|
1144
1164
|
expandPromptTemplates: options?.expandPromptTemplates ?? false,
|
|
1145
1165
|
streamingBehavior: options?.deliverAs,
|
|
@@ -1181,7 +1201,6 @@ export class AgentSession {
|
|
|
1181
1201
|
*/
|
|
1182
1202
|
async abort() {
|
|
1183
1203
|
this.abortRetry();
|
|
1184
|
-
this.abortCompaction();
|
|
1185
1204
|
this.agent.abort();
|
|
1186
1205
|
await this.waitForIdle();
|
|
1187
1206
|
}
|
|
@@ -1220,6 +1239,7 @@ export class AgentSession {
|
|
|
1220
1239
|
this.sessionManager.appendModelChange(model.provider, model.id);
|
|
1221
1240
|
if (options.persist) {
|
|
1222
1241
|
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
|
1242
|
+
this._addPersistedDefaultToNonEmptyScope(model);
|
|
1223
1243
|
}
|
|
1224
1244
|
// Apply thinking level for the new model.
|
|
1225
1245
|
// Per-model thinking level overrides take priority over the global default.
|
|
@@ -1227,6 +1247,20 @@ export class AgentSession {
|
|
|
1227
1247
|
this.setThinkingLevel(thinkingLevel);
|
|
1228
1248
|
await this._emitModelSelect(model, previousModel, "set");
|
|
1229
1249
|
}
|
|
1250
|
+
_addPersistedDefaultToNonEmptyScope(model) {
|
|
1251
|
+
if (this._scopedModels.length === 0)
|
|
1252
|
+
return;
|
|
1253
|
+
if (this._scopedModels.some((scoped) => modelsAreEqual(scoped.model, model)))
|
|
1254
|
+
return;
|
|
1255
|
+
this._scopedModels = [...this._scopedModels, { model }];
|
|
1256
|
+
const enabledModels = this.settingsManager.getEnabledModels();
|
|
1257
|
+
if (!enabledModels?.length)
|
|
1258
|
+
return;
|
|
1259
|
+
const modelReference = `${model.provider}/${model.id}`;
|
|
1260
|
+
if (enabledModels.some((pattern) => pattern.toLowerCase() === modelReference.toLowerCase()))
|
|
1261
|
+
return;
|
|
1262
|
+
this.settingsManager.setEnabledModels([...enabledModels, modelReference]);
|
|
1263
|
+
}
|
|
1230
1264
|
/**
|
|
1231
1265
|
* Cycle to next/previous model.
|
|
1232
1266
|
* Uses scoped models (from --models flag) if available, otherwise all available models.
|
|
@@ -1257,6 +1291,7 @@ export class AgentSession {
|
|
|
1257
1291
|
this.sessionManager.appendModelChange(next.model.provider, next.model.id);
|
|
1258
1292
|
if (options.persist) {
|
|
1259
1293
|
this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
|
|
1294
|
+
this._addPersistedDefaultToNonEmptyScope(next.model);
|
|
1260
1295
|
}
|
|
1261
1296
|
// Apply thinking level for the new model.
|
|
1262
1297
|
// - Explicit scoped model thinking level overrides defaults
|
|
@@ -1283,6 +1318,7 @@ export class AgentSession {
|
|
|
1283
1318
|
this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
|
|
1284
1319
|
if (options.persist) {
|
|
1285
1320
|
this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
|
|
1321
|
+
this._addPersistedDefaultToNonEmptyScope(nextModel);
|
|
1286
1322
|
}
|
|
1287
1323
|
// Apply thinking level for the new model.
|
|
1288
1324
|
// Model persistence does not implicitly rewrite the global thinking default.
|
|
@@ -1390,18 +1426,19 @@ export class AgentSession {
|
|
|
1390
1426
|
// =========================================================================
|
|
1391
1427
|
// Compaction
|
|
1392
1428
|
// =========================================================================
|
|
1393
|
-
/** Generate Pi's built-in manual compaction
|
|
1394
|
-
async _runDefaultCompaction(preparation, requestModel, apiKey, headers, customInstructions, signal, env, reason) {
|
|
1395
|
-
return compact(preparation, requestModel, apiKey, headers, customInstructions, signal, this.thinkingLevel,
|
|
1429
|
+
/** Generate Pi's built-in compaction summary for manual and automatic compaction. */
|
|
1430
|
+
async _runDefaultCompaction(preparation, requestModel, apiKey, headers, customInstructions, signal, env, reason, streamFunction = this.agent.streamFunction) {
|
|
1431
|
+
return compact(preparation, requestModel, apiKey, headers, customInstructions, signal, this.thinkingLevel, streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }), undefined);
|
|
1396
1432
|
}
|
|
1397
1433
|
/**
|
|
1398
1434
|
* Manually compact the session context.
|
|
1399
1435
|
*
|
|
1400
1436
|
* This is the manual entry point used by `/compact`, RPC, and extensions. It is
|
|
1401
|
-
* separate from automatic threshold/overflow compaction, which
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1404
|
-
* from `./compaction/index.ts`, unless the hook cancels or
|
|
1437
|
+
* separate from automatic threshold/overflow compaction, which enters through
|
|
1438
|
+
* `_checkCompaction()` and `_runAutoCompaction()`. After preparation and the
|
|
1439
|
+
* `session_before_compact` hook, both paths call the lower-level `compact()`
|
|
1440
|
+
* function imported from `./compaction/index.ts`, unless the hook cancels or
|
|
1441
|
+
* supplies a custom result.
|
|
1405
1442
|
*
|
|
1406
1443
|
* Aborts the current agent operation first. Manual compaction never retries or
|
|
1407
1444
|
* continues the interrupted agent turn.
|
|
@@ -1437,7 +1474,6 @@ export class AgentSession {
|
|
|
1437
1474
|
branchEntries: pathEntries,
|
|
1438
1475
|
customInstructions,
|
|
1439
1476
|
reason: "manual",
|
|
1440
|
-
timing: "manual",
|
|
1441
1477
|
willRetry: false,
|
|
1442
1478
|
signal: this._compactionAbortController.signal,
|
|
1443
1479
|
}));
|
|
@@ -1540,7 +1576,7 @@ export class AgentSession {
|
|
|
1540
1576
|
*/
|
|
1541
1577
|
abortCompaction() {
|
|
1542
1578
|
this._compactionAbortController?.abort();
|
|
1543
|
-
this.
|
|
1579
|
+
this._autoCompactionAbortController?.abort();
|
|
1544
1580
|
}
|
|
1545
1581
|
/**
|
|
1546
1582
|
* Cancel in-progress branch summarization.
|
|
@@ -1548,6 +1584,291 @@ export class AgentSession {
|
|
|
1548
1584
|
abortBranchSummary() {
|
|
1549
1585
|
this._branchSummaryAbortController?.abort();
|
|
1550
1586
|
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Dispatch automatic compaction after `agent_end` or before prompt submission.
|
|
1589
|
+
* Manual compaction does not call this method; it enters through `compact()`.
|
|
1590
|
+
*
|
|
1591
|
+
* Automatic cases:
|
|
1592
|
+
* 1. Overflow with retry: a context-overflow error or recoverable length stop;
|
|
1593
|
+
* remove the failed assistant message, compact, and retry the turn once.
|
|
1594
|
+
* 2. Overflow without retry: a successful response exceeded the configured
|
|
1595
|
+
* context window; compact but preserve the completed response.
|
|
1596
|
+
* 3. Threshold without retry: valid or estimated context usage crossed the
|
|
1597
|
+
* configured threshold; compact without retrying the completed response.
|
|
1598
|
+
*
|
|
1599
|
+
* Each case calls `_runAutoCompaction()`. After preparation and the
|
|
1600
|
+
* `session_before_compact` hook, that method calls the lower-level `compact()`
|
|
1601
|
+
* function imported from `./compaction/index.ts`, unless the hook cancels or
|
|
1602
|
+
* supplies a custom result.
|
|
1603
|
+
*
|
|
1604
|
+
* @param assistantMessage The assistant message to check
|
|
1605
|
+
* @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
|
|
1606
|
+
* @returns Whether the post-run loop should call `agent.continue()` for overflow recovery or queued messages
|
|
1607
|
+
*/
|
|
1608
|
+
async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
|
|
1609
|
+
const settings = this.settingsManager.getCompactionSettings();
|
|
1610
|
+
if (!settings.enabled)
|
|
1611
|
+
return false;
|
|
1612
|
+
// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
|
|
1613
|
+
if (skipAbortedCheck && assistantMessage.stopReason === "aborted")
|
|
1614
|
+
return false;
|
|
1615
|
+
const contextWindow = this.model?.contextWindow ?? 0;
|
|
1616
|
+
// Skip overflow check if the message came from a different model.
|
|
1617
|
+
// This handles the case where user switched from a smaller-context model (e.g. opus)
|
|
1618
|
+
// to a larger-context model (e.g. codex) - the overflow error from the old model
|
|
1619
|
+
// shouldn't trigger compaction for the new model.
|
|
1620
|
+
const sameModel = this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
|
|
1621
|
+
// Skip compaction checks if this assistant message is older than the latest
|
|
1622
|
+
// compaction boundary. This prevents a stale pre-compaction usage/error
|
|
1623
|
+
// from retriggering compaction on the first prompt after compaction.
|
|
1624
|
+
const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
|
|
1625
|
+
const assistantIsFromBeforeCompaction = compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
|
|
1626
|
+
if (assistantIsFromBeforeCompaction) {
|
|
1627
|
+
return false;
|
|
1628
|
+
}
|
|
1629
|
+
// Automatic cases 1 and 2: context overflow.
|
|
1630
|
+
// A length stop is recoverable when output ended below the model's original desired limit,
|
|
1631
|
+
// independent of the configured context size or any context-clamped provider request limit.
|
|
1632
|
+
const contextOverflow = sameModel && isContextOverflow(assistantMessage, contextWindow);
|
|
1633
|
+
const recoverableLength = sameModel && isRecoverableLength(assistantMessage, this.model?.maxTokens ?? 0);
|
|
1634
|
+
if (contextOverflow || recoverableLength) {
|
|
1635
|
+
const willRetry = assistantMessage.stopReason !== "stop";
|
|
1636
|
+
// Case 2: the response completed successfully. Compact, but do not retry because
|
|
1637
|
+
// agent.continue() cannot continue from a completed assistant response.
|
|
1638
|
+
if (!willRetry) {
|
|
1639
|
+
return await this._runAutoCompaction("overflow", false);
|
|
1640
|
+
}
|
|
1641
|
+
if (this._overflowRecoveryAttempted) {
|
|
1642
|
+
const errorMessage = contextOverflow
|
|
1643
|
+
? "Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model."
|
|
1644
|
+
: "Truncated response recovery failed after one compact-and-retry attempt.";
|
|
1645
|
+
this._emit({
|
|
1646
|
+
type: "compaction_end",
|
|
1647
|
+
reason: "overflow",
|
|
1648
|
+
result: undefined,
|
|
1649
|
+
aborted: false,
|
|
1650
|
+
willRetry: false,
|
|
1651
|
+
errorMessage,
|
|
1652
|
+
});
|
|
1653
|
+
await this._emitSessionCompactFailed({
|
|
1654
|
+
reason: "overflow",
|
|
1655
|
+
errorMessage,
|
|
1656
|
+
aborted: false,
|
|
1657
|
+
willRetry: false,
|
|
1658
|
+
fromExtension: false,
|
|
1659
|
+
});
|
|
1660
|
+
return false;
|
|
1661
|
+
}
|
|
1662
|
+
// Case 1: remove the failed or truncated message from agent state, compact, and
|
|
1663
|
+
// retry once. The message remains in session history but is excluded from retry context.
|
|
1664
|
+
this._overflowRecoveryAttempted = true;
|
|
1665
|
+
const messages = this.agent.state.messages;
|
|
1666
|
+
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
1667
|
+
this.agent.state.messages = messages.slice(0, -1);
|
|
1668
|
+
}
|
|
1669
|
+
return await this._runAutoCompaction("overflow", willRetry);
|
|
1670
|
+
}
|
|
1671
|
+
// Case 3: threshold compaction without retry.
|
|
1672
|
+
// For error messages or all-zero usage messages, estimate from the last valid response.
|
|
1673
|
+
// This ensures sessions that hit persistent API errors (e.g. 529) or malformed zero-usage
|
|
1674
|
+
// responses can still compact and do not reset context accounting.
|
|
1675
|
+
let contextTokens;
|
|
1676
|
+
const directContextTokens = assistantMessage.usage ? calculateContextTokens(assistantMessage.usage) : 0;
|
|
1677
|
+
if (assistantMessage.stopReason === "error" || directContextTokens === 0) {
|
|
1678
|
+
const messages = this.agent.state.messages;
|
|
1679
|
+
const estimate = estimateContextTokens(messages);
|
|
1680
|
+
// Without provider usage, estimate.tokens is the pure message-size estimate.
|
|
1681
|
+
// Only usage-backed estimates need the stale pre-compaction check.
|
|
1682
|
+
if (estimate.lastUsageIndex !== null) {
|
|
1683
|
+
// Verify the usage source is post-compaction. Kept pre-compaction messages
|
|
1684
|
+
// have stale usage reflecting the old (larger) context and would falsely
|
|
1685
|
+
// trigger compaction right after one just finished.
|
|
1686
|
+
const usageMsg = messages[estimate.lastUsageIndex];
|
|
1687
|
+
if (compactionEntry &&
|
|
1688
|
+
usageMsg.role === "assistant" &&
|
|
1689
|
+
usageMsg.timestamp <= new Date(compactionEntry.timestamp).getTime()) {
|
|
1690
|
+
return false;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
contextTokens = estimate.tokens;
|
|
1694
|
+
}
|
|
1695
|
+
else {
|
|
1696
|
+
contextTokens = directContextTokens;
|
|
1697
|
+
}
|
|
1698
|
+
if (shouldCompact(contextTokens, contextWindow, settings)) {
|
|
1699
|
+
return await this._runAutoCompaction("threshold", false);
|
|
1700
|
+
}
|
|
1701
|
+
return false;
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Execute threshold or overflow compaction. Manual compaction uses
|
|
1705
|
+
* `AgentSession.compact()` instead. Both paths call the lower-level `compact()`
|
|
1706
|
+
* function imported from `./compaction/index.ts` after preparation and extension
|
|
1707
|
+
* interception.
|
|
1708
|
+
*
|
|
1709
|
+
* @param reason Automatic trigger selected by `_checkCompaction()`
|
|
1710
|
+
* @param willRetry Whether to continue the interrupted turn after overflow compaction
|
|
1711
|
+
* @returns Whether the post-run loop should call `agent.continue()`
|
|
1712
|
+
*/
|
|
1713
|
+
async _runAutoCompaction(reason, willRetry) {
|
|
1714
|
+
const settings = this.settingsManager.getCompactionSettings();
|
|
1715
|
+
let started = false;
|
|
1716
|
+
let fromExtension = false;
|
|
1717
|
+
try {
|
|
1718
|
+
if (!this.model) {
|
|
1719
|
+
return false;
|
|
1720
|
+
}
|
|
1721
|
+
// Capture before awaiting auth: a model switch during the await would leave the
|
|
1722
|
+
// resolved auth and the summary generator disagreeing about the stream function.
|
|
1723
|
+
const streamFunction = this.agent.streamFunction;
|
|
1724
|
+
const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(this.model, streamFunction);
|
|
1725
|
+
const pathEntries = this.sessionManager.getBranch();
|
|
1726
|
+
const preparation = prepareCompaction(pathEntries, settings);
|
|
1727
|
+
if (!preparation) {
|
|
1728
|
+
return false;
|
|
1729
|
+
}
|
|
1730
|
+
this._emit({ type: "compaction_start", reason });
|
|
1731
|
+
this._autoCompactionAbortController = new AbortController();
|
|
1732
|
+
started = true;
|
|
1733
|
+
let extensionCompaction;
|
|
1734
|
+
if (this._extensionRunner.hasHandlers("session_before_compact")) {
|
|
1735
|
+
const extensionResult = (await this._extensionRunner.emit({
|
|
1736
|
+
type: "session_before_compact",
|
|
1737
|
+
preparation,
|
|
1738
|
+
branchEntries: pathEntries,
|
|
1739
|
+
customInstructions: undefined,
|
|
1740
|
+
reason,
|
|
1741
|
+
willRetry,
|
|
1742
|
+
signal: this._autoCompactionAbortController.signal,
|
|
1743
|
+
}));
|
|
1744
|
+
if (extensionResult?.cancel) {
|
|
1745
|
+
this._emit({
|
|
1746
|
+
type: "compaction_end",
|
|
1747
|
+
reason,
|
|
1748
|
+
result: undefined,
|
|
1749
|
+
aborted: true,
|
|
1750
|
+
willRetry: false,
|
|
1751
|
+
});
|
|
1752
|
+
await this._emitSessionCompactFailed({
|
|
1753
|
+
reason,
|
|
1754
|
+
aborted: true,
|
|
1755
|
+
willRetry: false,
|
|
1756
|
+
fromExtension: false,
|
|
1757
|
+
});
|
|
1758
|
+
return false;
|
|
1759
|
+
}
|
|
1760
|
+
if (extensionResult?.compaction) {
|
|
1761
|
+
extensionCompaction = extensionResult.compaction;
|
|
1762
|
+
fromExtension = true;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
let summary;
|
|
1766
|
+
let firstKeptEntryId;
|
|
1767
|
+
let tokensBefore;
|
|
1768
|
+
let usage;
|
|
1769
|
+
let details;
|
|
1770
|
+
if (extensionCompaction) {
|
|
1771
|
+
// Extension provided compaction content
|
|
1772
|
+
summary = extensionCompaction.summary;
|
|
1773
|
+
firstKeptEntryId = extensionCompaction.firstKeptEntryId;
|
|
1774
|
+
tokensBefore = extensionCompaction.tokensBefore;
|
|
1775
|
+
usage = extensionCompaction.usage;
|
|
1776
|
+
details = extensionCompaction.details;
|
|
1777
|
+
}
|
|
1778
|
+
else {
|
|
1779
|
+
// Shared default summary generator, also used by manual compaction.
|
|
1780
|
+
const compactResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, undefined, this._autoCompactionAbortController.signal, env, reason, streamFunction);
|
|
1781
|
+
summary = compactResult.summary;
|
|
1782
|
+
firstKeptEntryId = compactResult.firstKeptEntryId;
|
|
1783
|
+
tokensBefore = compactResult.tokensBefore;
|
|
1784
|
+
usage = compactResult.usage;
|
|
1785
|
+
details = compactResult.details;
|
|
1786
|
+
}
|
|
1787
|
+
if (this._autoCompactionAbortController.signal.aborted) {
|
|
1788
|
+
this._emit({
|
|
1789
|
+
type: "compaction_end",
|
|
1790
|
+
reason,
|
|
1791
|
+
result: undefined,
|
|
1792
|
+
aborted: true,
|
|
1793
|
+
willRetry: false,
|
|
1794
|
+
});
|
|
1795
|
+
await this._emitSessionCompactFailed({
|
|
1796
|
+
reason,
|
|
1797
|
+
aborted: true,
|
|
1798
|
+
willRetry: false,
|
|
1799
|
+
fromExtension,
|
|
1800
|
+
});
|
|
1801
|
+
return false;
|
|
1802
|
+
}
|
|
1803
|
+
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
|
|
1804
|
+
const newEntries = this.sessionManager.getEntries();
|
|
1805
|
+
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1806
|
+
this.agent.state.messages = sessionContext.messages;
|
|
1807
|
+
const estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
|
|
1808
|
+
// Get the saved compaction entry for the extension event
|
|
1809
|
+
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
1810
|
+
if (this._extensionRunner && savedCompactionEntry) {
|
|
1811
|
+
await this._extensionRunner.emit({
|
|
1812
|
+
type: "session_compact",
|
|
1813
|
+
compactionEntry: savedCompactionEntry,
|
|
1814
|
+
fromExtension,
|
|
1815
|
+
reason,
|
|
1816
|
+
willRetry,
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
const result = {
|
|
1820
|
+
summary,
|
|
1821
|
+
firstKeptEntryId,
|
|
1822
|
+
tokensBefore,
|
|
1823
|
+
estimatedTokensAfter,
|
|
1824
|
+
usage,
|
|
1825
|
+
details,
|
|
1826
|
+
};
|
|
1827
|
+
this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
|
|
1828
|
+
if (willRetry) {
|
|
1829
|
+
const messages = this.agent.state.messages;
|
|
1830
|
+
const lastMsg = messages[messages.length - 1];
|
|
1831
|
+
// The overflow response was persisted on message_end before _checkCompaction() removed it
|
|
1832
|
+
// from agent state. Rebuilding state from the new compaction can restore that kept entry,
|
|
1833
|
+
// leaving an assistant as the final message. agent.continue() rejects that state, so remove
|
|
1834
|
+
// the retriable error or truncated-length response again before continuing the interrupted turn.
|
|
1835
|
+
if (lastMsg?.role === "assistant" && (lastMsg.stopReason === "error" || lastMsg.stopReason === "length")) {
|
|
1836
|
+
this.agent.state.messages = messages.slice(0, -1);
|
|
1837
|
+
}
|
|
1838
|
+
return true;
|
|
1839
|
+
}
|
|
1840
|
+
// Auto-compaction can complete while follow-up/steering/custom messages are waiting.
|
|
1841
|
+
// Continue once so queued messages are delivered.
|
|
1842
|
+
return this.agent.hasQueuedMessages();
|
|
1843
|
+
}
|
|
1844
|
+
catch (error) {
|
|
1845
|
+
const errorMessage = error instanceof Error ? error.message : "compaction failed";
|
|
1846
|
+
if (started) {
|
|
1847
|
+
const formattedErrorMessage = reason === "overflow"
|
|
1848
|
+
? `Context overflow recovery failed: ${errorMessage}`
|
|
1849
|
+
: `Auto-compaction failed: ${errorMessage}`;
|
|
1850
|
+
this._emit({
|
|
1851
|
+
type: "compaction_end",
|
|
1852
|
+
reason,
|
|
1853
|
+
result: undefined,
|
|
1854
|
+
aborted: false,
|
|
1855
|
+
willRetry: false,
|
|
1856
|
+
errorMessage: formattedErrorMessage,
|
|
1857
|
+
});
|
|
1858
|
+
await this._emitSessionCompactFailed({
|
|
1859
|
+
reason,
|
|
1860
|
+
errorMessage: formattedErrorMessage,
|
|
1861
|
+
aborted: false,
|
|
1862
|
+
willRetry: false,
|
|
1863
|
+
fromExtension,
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
return false;
|
|
1867
|
+
}
|
|
1868
|
+
finally {
|
|
1869
|
+
this._autoCompactionAbortController = undefined;
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1551
1872
|
/**
|
|
1552
1873
|
* Toggle auto-compaction setting.
|
|
1553
1874
|
*/
|
|
@@ -1742,10 +2063,6 @@ export class AgentSession {
|
|
|
1742
2063
|
},
|
|
1743
2064
|
getSystemPrompt: () => this.systemPrompt,
|
|
1744
2065
|
getSystemPromptOptions: () => this._baseSystemPromptOptions,
|
|
1745
|
-
getShellSettings: () => ({
|
|
1746
|
-
shellPath: this.settingsManager.getShellPath(),
|
|
1747
|
-
commandPrefix: this.settingsManager.getShellCommandPrefix(),
|
|
1748
|
-
}),
|
|
1749
2066
|
}, {
|
|
1750
2067
|
registerProvider: (name, config) => {
|
|
1751
2068
|
this._modelRuntime.registerProvider(name, config);
|