@earendil-works/pi-coding-agent 0.84.2 → 0.84.4
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 +127 -0
- package/README.md +13 -8
- package/dist/bundle/chunks/anthropic-messages-JWX2WP65.js +45 -0
- package/dist/bundle/chunks/anthropic.js +75 -0
- package/dist/bundle/chunks/azure-openai-responses-5ENVCQ7U.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +74 -0
- package/dist/bundle/chunks/chunk-2KVJKXS2.js +50 -0
- package/dist/bundle/chunks/chunk-4L3WN2XY.js +2 -0
- package/dist/bundle/chunks/chunk-6CYCI3EJ.js +12 -0
- package/dist/bundle/chunks/chunk-6R7UI4IY.js +2 -0
- package/dist/bundle/chunks/chunk-AXIIZGTV.js +2 -0
- package/dist/bundle/chunks/chunk-DRQDKNXA.js +2 -0
- package/dist/bundle/chunks/chunk-GMWCTUPB.js +2 -0
- package/dist/bundle/chunks/chunk-JUUC5AZY.js +2 -0
- package/dist/bundle/chunks/chunk-L2ZPNLYD.js +2 -0
- package/dist/bundle/chunks/chunk-MNAIPA3J.js +2 -0
- package/dist/bundle/chunks/chunk-NBBFIJUL.js +11 -0
- package/dist/bundle/chunks/chunk-NUHFSC37.js +25 -0
- package/dist/bundle/chunks/chunk-OMWWHBTG.js +1548 -0
- package/dist/bundle/chunks/chunk-PGXDEOVV.js +2 -0
- package/dist/bundle/chunks/chunk-PJACZCIR.js +2 -0
- package/dist/bundle/chunks/chunk-PRRNXB7S.js +2 -0
- package/dist/bundle/chunks/chunk-TEPMHNKQ.js +2 -0
- package/dist/bundle/chunks/chunk-TRDNDS6A.js +4 -0
- package/dist/bundle/chunks/chunk-UAQELI3K.js +2 -0
- package/dist/bundle/chunks/chunk-XDUWOHPL.js +6 -0
- package/dist/bundle/chunks/chunk-XNGRGP62.js +2 -0
- package/dist/bundle/chunks/github-copilot.js +2 -0
- package/dist/bundle/chunks/google-generative-ai-YMUPJBKR.js +2 -0
- package/dist/bundle/chunks/google-vertex-MPWMV4OF.js +2 -0
- package/dist/bundle/chunks/https-proxy-agent-2VXB7436.js +13 -0
- package/dist/bundle/chunks/image-resize-worker.js +2 -0
- package/dist/bundle/chunks/kimi-coding.js +2 -0
- package/dist/bundle/chunks/lib-HGQMNYVT.js +9 -0
- package/dist/bundle/chunks/mistral-conversations-Q3AWZJAZ.js +5 -0
- package/dist/bundle/chunks/multipart-parser-SQ6GDJL7.js +3 -0
- package/dist/bundle/chunks/openai-codex-responses-GJVBJXLB.js +8 -0
- package/dist/bundle/chunks/openai-codex.js +75 -0
- package/dist/bundle/chunks/openai-completions-ERMU2SS7.js +7 -0
- package/dist/bundle/chunks/openai-responses-BSOJMSEV.js +2 -0
- package/dist/bundle/chunks/openrouter-images-N4XVVFSD.js +2 -0
- package/dist/bundle/chunks/openrouter.js +75 -0
- package/dist/bundle/chunks/pi-messages-TPFH44NP.js +8 -0
- package/dist/bundle/chunks/radius.js +75 -0
- package/dist/bundle/chunks/src-UK77SYZE.js +4 -0
- package/dist/bundle/chunks/xai.js +2 -0
- package/dist/bundle/cli.js +3 -0
- package/dist/bundle/client.js +2 -0
- package/dist/bundle/index.js +2 -0
- package/dist/bundle/rpc-entry.js +3 -0
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +29 -9
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +2 -1
- package/dist/cli/file-processor.js.map +1 -1
- 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/config.d.ts +4 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +22 -14
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +38 -8
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +240 -104
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +6 -7
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +7 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +9 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +59 -36
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/defaults.d.ts +1 -0
- package/dist/core/defaults.d.ts.map +1 -1
- package/dist/core/defaults.js +9 -0
- package/dist/core/defaults.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +115 -48
- package/dist/core/extensions/loader.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 +45 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +56 -10
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +3 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/keybindings.d.ts +20 -19
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +26 -5
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-config.d.ts +5 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +3 -1
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +14 -8
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/models-store.d.ts.map +1 -1
- package/dist/core/models-store.js +2 -1
- package/dist/core/models-store.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +22 -14
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/pi-manifest.d.ts.map +1 -1
- package/dist/core/pi-manifest.js +2 -1
- package/dist/core/pi-manifest.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +2 -1
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +3 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +10 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-export.d.ts +4 -0
- package/dist/core/session-export.d.ts.map +1 -0
- package/dist/core/session-export.js +32 -0
- package/dist/core/session-export.js.map +1 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +6 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-diagnostics.d.ts +9 -0
- package/dist/core/settings-diagnostics.d.ts.map +1 -0
- package/dist/core/settings-diagnostics.js +21 -0
- package/dist/core/settings-diagnostics.js.map +1 -0
- package/dist/core/settings-manager.d.ts +17 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +69 -10
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +51 -33
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +2 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +11 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +14 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +36 -20
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +0 -5
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +2 -5
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +20 -4
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/index.d.ts +4 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +18 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/powershell.d.ts +16 -0
- package/dist/core/tools/powershell.d.ts.map +1 -0
- package/dist/core/tools/powershell.js +39 -0
- package/dist/core/tools/powershell.js.map +1 -0
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +2 -1
- package/dist/core/trust-manager.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/index.d.ts.map +1 -1
- package/dist/extensions/llama/index.js +2 -0
- package/dist/extensions/llama/index.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 +29 -4
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +4 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +20 -15
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +4 -3
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +9 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +56 -12
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +9 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +128 -56
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
- package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
- package/dist/modes/interactive/components/settings-submenu.js +165 -0
- package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
- package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +74 -17
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +2 -1
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +15 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +223 -129
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/session-share.d.ts +16 -0
- package/dist/modes/interactive/session-share.d.ts.map +1 -0
- package/dist/modes/interactive/session-share.js +188 -0
- package/dist/modes/interactive/session-share.js.map +1 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +4 -3
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +16 -13
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +20 -6
- package/dist/modes/json-event.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/package-manager-cli.d.ts +1 -0
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +186 -2
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +2 -1
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/management-http.d.ts +5 -3
- package/dist/utils/management-http.d.ts.map +1 -1
- package/dist/utils/management-http.js +14 -10
- package/dist/utils/management-http.js.map +1 -1
- package/dist/utils/shell.d.ts +3 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +23 -9
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts +1 -0
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +60 -1
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/dist/utils/text.d.ts +8 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +9 -0
- package/dist/utils/text.js.map +1 -0
- package/docs/compaction.md +17 -0
- package/docs/custom-provider.md +12 -9
- package/docs/environment-variables.md +11 -8
- package/docs/extensions.md +35 -7
- package/docs/json.md +7 -2
- package/docs/keybindings.md +9 -9
- package/docs/llama-cpp.md +2 -0
- package/docs/models.md +8 -2
- package/docs/packages.md +2 -2
- package/docs/quickstart.md +1 -1
- package/docs/rpc.md +32 -3
- package/docs/sdk.md +7 -2
- package/docs/settings.md +23 -7
- package/docs/skills.md +4 -3
- package/docs/terminal-setup.md +23 -9
- package/docs/usage.md +10 -5
- package/docs/windows.md +24 -2
- package/examples/extensions/border-status-editor.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/git-checkpoint.ts +2 -2
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/notify.ts +3 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +1 -1
- package/examples/extensions/subagent/index.ts +6 -1
- package/examples/extensions/titlebar-spinner.ts +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/rpc-extension-ui.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +24 -77
- package/package.json +12 -14
|
@@ -12,26 +12,26 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Modes use this class and add their own I/O layer on top.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
16
|
import { basename, dirname } from "node:path";
|
|
17
17
|
import { contentText } from "@earendil-works/pi-ai";
|
|
18
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
|
-
import { resolvePath } from "../utils/paths.js";
|
|
22
21
|
import { sleep } from "../utils/sleep.js";
|
|
23
22
|
import { normalizeToolResultImages } from "../utils/tool-result-images.js";
|
|
24
23
|
import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
|
|
25
24
|
import { executeBashWithOperations } from "./bash-executor.js";
|
|
26
25
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
27
|
-
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
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";
|
|
30
29
|
import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
|
|
31
30
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
32
31
|
import { ModelRegistry } from "./model-registry.js";
|
|
33
32
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
34
|
-
import {
|
|
33
|
+
import { exportSessionToJsonl } from "./session-export.js";
|
|
34
|
+
import { getLatestCompactionEntry } from "./session-manager.js";
|
|
35
35
|
import { createSyntheticSourceInfo } from "./source-info.js";
|
|
36
36
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
37
37
|
import { createLocalBashOperations } from "./tools/bash.js";
|
|
@@ -71,8 +71,6 @@ function estimateMessagesTokens(messages) {
|
|
|
71
71
|
// ============================================================================
|
|
72
72
|
// Constants
|
|
73
73
|
// ============================================================================
|
|
74
|
-
/** Standard thinking levels */
|
|
75
|
-
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
76
74
|
// ============================================================================
|
|
77
75
|
// AgentSession Class
|
|
78
76
|
// ============================================================================
|
|
@@ -93,6 +91,8 @@ export class AgentSession {
|
|
|
93
91
|
_followUpMessages = [];
|
|
94
92
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
95
93
|
_pendingNextTurnMessages = [];
|
|
94
|
+
/** Context-only custom messages queued during a run, flushed once the current turn's tool results are in. */
|
|
95
|
+
_pendingCustomMessages = [];
|
|
96
96
|
// Compaction state
|
|
97
97
|
_compactionAbortController = undefined;
|
|
98
98
|
_autoCompactionAbortController = undefined;
|
|
@@ -271,18 +271,33 @@ export class AgentSession {
|
|
|
271
271
|
};
|
|
272
272
|
};
|
|
273
273
|
}
|
|
274
|
+
async _compactBeforeNextAssistantResponse(context) {
|
|
275
|
+
const model = this.model;
|
|
276
|
+
const settings = this.settingsManager.getCompactionSettings();
|
|
277
|
+
if (!model ||
|
|
278
|
+
model.contextWindow <= 0 ||
|
|
279
|
+
!shouldCompact(estimateContextTokens(context.messages).tokens, model.contextWindow, settings)) {
|
|
280
|
+
return context;
|
|
281
|
+
}
|
|
282
|
+
await this._runAutoCompaction("threshold", false);
|
|
283
|
+
return {
|
|
284
|
+
...context,
|
|
285
|
+
messages: this.agent.state.messages.slice(),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
274
288
|
_installAgentNextTurnRefresh() {
|
|
275
289
|
const previousPrepareNextTurnWithContext = this.agent.prepareNextTurnWithContext ??
|
|
276
290
|
(this.agent.prepareNextTurn
|
|
277
291
|
? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
|
|
278
292
|
: undefined);
|
|
279
293
|
this.agent.prepareNextTurnWithContext = async (turn, signal) => {
|
|
280
|
-
const
|
|
281
|
-
const
|
|
294
|
+
const context = await this._compactBeforeNextAssistantResponse(turn.context);
|
|
295
|
+
const previousSnapshot = await previousPrepareNextTurnWithContext?.({ ...turn, context }, signal);
|
|
296
|
+
const nextContext = previousSnapshot?.context ?? context;
|
|
282
297
|
return {
|
|
283
298
|
...previousSnapshot,
|
|
284
299
|
context: {
|
|
285
|
-
...
|
|
300
|
+
...nextContext,
|
|
286
301
|
systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
|
|
287
302
|
tools: this.agent.state.tools.slice(),
|
|
288
303
|
},
|
|
@@ -307,6 +322,11 @@ export class AgentSession {
|
|
|
307
322
|
followUp: [...this._followUpMessages],
|
|
308
323
|
});
|
|
309
324
|
}
|
|
325
|
+
async _emitSessionCompactFailed(event) {
|
|
326
|
+
if (this._extensionRunner.hasHandlers("session_compact_failed")) {
|
|
327
|
+
await this._extensionRunner.emit({ type: "session_compact_failed", ...event });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
310
330
|
_getIdleWaitPromise() {
|
|
311
331
|
if (!this._idleWaitPromise) {
|
|
312
332
|
this._idleWaitPromise = new Promise((resolve) => {
|
|
@@ -397,6 +417,14 @@ export class AgentSession {
|
|
|
397
417
|
}
|
|
398
418
|
}
|
|
399
419
|
}
|
|
420
|
+
// A turn ends after its assistant message and every tool result has been appended,
|
|
421
|
+
// so this is the first point in the run where a context-only custom message can be
|
|
422
|
+
// inserted without landing between a tool call and its result. Flushing after the
|
|
423
|
+
// extension and listener dispatch above also picks up messages that turn_end
|
|
424
|
+
// handlers queued.
|
|
425
|
+
if (event.type === "turn_end") {
|
|
426
|
+
this._flushPendingCustomMessages();
|
|
427
|
+
}
|
|
400
428
|
};
|
|
401
429
|
_willRetryAfterAgentEnd(event) {
|
|
402
430
|
const settings = this.settingsManager.getRetrySettings();
|
|
@@ -752,6 +780,7 @@ export class AgentSession {
|
|
|
752
780
|
finally {
|
|
753
781
|
this._systemPromptOverride = undefined;
|
|
754
782
|
this._flushPendingBashMessages();
|
|
783
|
+
this._flushPendingCustomMessages();
|
|
755
784
|
await this._emitAgentSettled();
|
|
756
785
|
}
|
|
757
786
|
}
|
|
@@ -841,8 +870,9 @@ export class AgentSession {
|
|
|
841
870
|
preflightResult?.(true);
|
|
842
871
|
return;
|
|
843
872
|
}
|
|
844
|
-
// Flush any pending bash messages before the new prompt
|
|
873
|
+
// Flush any pending bash and custom messages before the new prompt
|
|
845
874
|
this._flushPendingBashMessages();
|
|
875
|
+
this._flushPendingCustomMessages();
|
|
846
876
|
// Validate model
|
|
847
877
|
if (!this.model) {
|
|
848
878
|
throw new Error(formatNoModelSelectedMessage());
|
|
@@ -1056,8 +1086,9 @@ export class AgentSession {
|
|
|
1056
1086
|
/**
|
|
1057
1087
|
* Send a custom message to the session. Creates a CustomMessageEntry.
|
|
1058
1088
|
*
|
|
1059
|
-
* Handles
|
|
1089
|
+
* Handles four cases:
|
|
1060
1090
|
* - Streaming: queues message, processed when loop pulls from queue
|
|
1091
|
+
* - Streaming + triggerTurn false: appended to state/session once the current turn ends
|
|
1061
1092
|
* - Not streaming + triggerTurn: appends to state/session, starts new turn
|
|
1062
1093
|
* - Not streaming + no trigger: appends to state/session, no turn
|
|
1063
1094
|
*
|
|
@@ -1089,11 +1120,34 @@ export class AgentSession {
|
|
|
1089
1120
|
else if (options?.triggerTurn) {
|
|
1090
1121
|
await this._runAgentPrompt(appMessage);
|
|
1091
1122
|
}
|
|
1123
|
+
else if (this.isStreaming) {
|
|
1124
|
+
// Appending now would put the message between an assistant tool call and its
|
|
1125
|
+
// result, which providers that validate message order reject on replay. Defer
|
|
1126
|
+
// to the end of the turn. Nothing is emitted yet: message events must not
|
|
1127
|
+
// describe messages the session tree does not contain.
|
|
1128
|
+
this._pendingCustomMessages.push(appMessage);
|
|
1129
|
+
}
|
|
1092
1130
|
else {
|
|
1093
|
-
this.
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1131
|
+
this._appendCustomMessage(appMessage);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
_appendCustomMessage(appMessage) {
|
|
1135
|
+
this.agent.state.messages.push(appMessage);
|
|
1136
|
+
this.sessionManager.appendCustomMessageEntry(appMessage.customType, appMessage.content, appMessage.display, appMessage.details);
|
|
1137
|
+
this._emit({ type: "message_start", message: appMessage });
|
|
1138
|
+
this._emit({ type: "message_end", message: appMessage });
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Append custom messages queued while the agent was running.
|
|
1142
|
+
* Called once the current turn's tool results are in agent state and session history.
|
|
1143
|
+
*/
|
|
1144
|
+
_flushPendingCustomMessages() {
|
|
1145
|
+
if (this._pendingCustomMessages.length === 0)
|
|
1146
|
+
return;
|
|
1147
|
+
const pending = this._pendingCustomMessages;
|
|
1148
|
+
this._pendingCustomMessages = [];
|
|
1149
|
+
for (const appMessage of pending) {
|
|
1150
|
+
this._appendCustomMessage(appMessage);
|
|
1097
1151
|
}
|
|
1098
1152
|
}
|
|
1099
1153
|
/**
|
|
@@ -1191,35 +1245,55 @@ export class AgentSession {
|
|
|
1191
1245
|
}
|
|
1192
1246
|
/**
|
|
1193
1247
|
* Set model directly.
|
|
1194
|
-
* Validates that auth is configured
|
|
1248
|
+
* Validates that auth is configured and saves to the session transcript.
|
|
1249
|
+
* Persists to global defaults only when options.persist is true.
|
|
1195
1250
|
* @throws Error if no auth is configured for the model
|
|
1196
1251
|
*/
|
|
1197
|
-
async setModel(model) {
|
|
1252
|
+
async setModel(model, options = {}) {
|
|
1198
1253
|
if (!(await this._modelRuntime.checkAuth(model.provider))) {
|
|
1199
1254
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
1200
1255
|
}
|
|
1201
1256
|
const previousModel = this.model;
|
|
1202
|
-
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1257
|
+
const thinkingLevel = this._getThinkingLevelForModelSwitch(model);
|
|
1203
1258
|
this.agent.state.model = model;
|
|
1204
1259
|
this.sessionManager.appendModelChange(model.provider, model.id);
|
|
1205
|
-
|
|
1206
|
-
|
|
1260
|
+
if (options.persist) {
|
|
1261
|
+
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
|
1262
|
+
this._addPersistedDefaultToNonEmptyScope(model);
|
|
1263
|
+
}
|
|
1264
|
+
// Apply thinking level for the new model.
|
|
1265
|
+
// Per-model thinking level overrides take priority over the global default.
|
|
1266
|
+
// Model persistence does not implicitly rewrite the global thinking default.
|
|
1207
1267
|
this.setThinkingLevel(thinkingLevel);
|
|
1208
1268
|
await this._emitModelSelect(model, previousModel, "set");
|
|
1209
1269
|
}
|
|
1270
|
+
_addPersistedDefaultToNonEmptyScope(model) {
|
|
1271
|
+
if (this._scopedModels.length === 0)
|
|
1272
|
+
return;
|
|
1273
|
+
if (this._scopedModels.some((scoped) => modelsAreEqual(scoped.model, model)))
|
|
1274
|
+
return;
|
|
1275
|
+
this._scopedModels = [...this._scopedModels, { model }];
|
|
1276
|
+
const enabledModels = this.settingsManager.getEnabledModels();
|
|
1277
|
+
if (!enabledModels?.length)
|
|
1278
|
+
return;
|
|
1279
|
+
const modelReference = `${model.provider}/${model.id}`;
|
|
1280
|
+
if (enabledModels.some((pattern) => pattern.toLowerCase() === modelReference.toLowerCase()))
|
|
1281
|
+
return;
|
|
1282
|
+
this.settingsManager.setEnabledModels([...enabledModels, modelReference]);
|
|
1283
|
+
}
|
|
1210
1284
|
/**
|
|
1211
1285
|
* Cycle to next/previous model.
|
|
1212
1286
|
* Uses scoped models (from --models flag) if available, otherwise all available models.
|
|
1213
1287
|
* @param direction - "forward" (default) or "backward"
|
|
1214
1288
|
* @returns The new model info, or undefined if only one model available
|
|
1215
1289
|
*/
|
|
1216
|
-
async cycleModel(direction = "forward") {
|
|
1290
|
+
async cycleModel(direction = "forward", options = {}) {
|
|
1217
1291
|
if (this._scopedModels.length > 0) {
|
|
1218
|
-
return this._cycleScopedModel(direction);
|
|
1292
|
+
return this._cycleScopedModel(direction, options);
|
|
1219
1293
|
}
|
|
1220
|
-
return this._cycleAvailableModel(direction);
|
|
1294
|
+
return this._cycleAvailableModel(direction, options);
|
|
1221
1295
|
}
|
|
1222
|
-
async _cycleScopedModel(direction) {
|
|
1296
|
+
async _cycleScopedModel(direction, options) {
|
|
1223
1297
|
const availableIds = new Set(this._modelRuntime.getAvailableSnapshot().map((model) => `${model.provider}\0${model.id}`));
|
|
1224
1298
|
const scopedModels = this._scopedModels.filter((scoped) => availableIds.has(`${scoped.model.provider}\0${scoped.model.id}`));
|
|
1225
1299
|
if (scopedModels.length <= 1)
|
|
@@ -1231,20 +1305,24 @@ export class AgentSession {
|
|
|
1231
1305
|
const len = scopedModels.length;
|
|
1232
1306
|
const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
|
|
1233
1307
|
const next = scopedModels[nextIndex];
|
|
1234
|
-
const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
|
|
1308
|
+
const thinkingLevel = this._getThinkingLevelForModelSwitch(next.model, next.thinkingLevel);
|
|
1235
1309
|
// Apply model
|
|
1236
1310
|
this.agent.state.model = next.model;
|
|
1237
1311
|
this.sessionManager.appendModelChange(next.model.provider, next.model.id);
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1312
|
+
if (options.persist) {
|
|
1313
|
+
this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
|
|
1314
|
+
this._addPersistedDefaultToNonEmptyScope(next.model);
|
|
1315
|
+
}
|
|
1316
|
+
// Apply thinking level for the new model.
|
|
1317
|
+
// - Explicit scoped model thinking level overrides defaults
|
|
1318
|
+
// - Per-model thinking level overrides take priority over the global default
|
|
1242
1319
|
// setThinkingLevel clamps to model capabilities.
|
|
1320
|
+
// Model persistence does not implicitly rewrite the global thinking default.
|
|
1243
1321
|
this.setThinkingLevel(thinkingLevel);
|
|
1244
1322
|
await this._emitModelSelect(next.model, currentModel, "cycle");
|
|
1245
1323
|
return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
|
|
1246
1324
|
}
|
|
1247
|
-
async _cycleAvailableModel(direction) {
|
|
1325
|
+
async _cycleAvailableModel(direction, options) {
|
|
1248
1326
|
const availableModels = this._modelRuntime.getAvailableSnapshot();
|
|
1249
1327
|
if (availableModels.length <= 1)
|
|
1250
1328
|
return undefined;
|
|
@@ -1255,11 +1333,15 @@ export class AgentSession {
|
|
|
1255
1333
|
const len = availableModels.length;
|
|
1256
1334
|
const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
|
|
1257
1335
|
const nextModel = availableModels[nextIndex];
|
|
1258
|
-
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1336
|
+
const thinkingLevel = this._getThinkingLevelForModelSwitch(nextModel);
|
|
1259
1337
|
this.agent.state.model = nextModel;
|
|
1260
1338
|
this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
|
|
1261
|
-
|
|
1262
|
-
|
|
1339
|
+
if (options.persist) {
|
|
1340
|
+
this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
|
|
1341
|
+
this._addPersistedDefaultToNonEmptyScope(nextModel);
|
|
1342
|
+
}
|
|
1343
|
+
// Apply thinking level for the new model.
|
|
1344
|
+
// Model persistence does not implicitly rewrite the global thinking default.
|
|
1263
1345
|
this.setThinkingLevel(thinkingLevel);
|
|
1264
1346
|
await this._emitModelSelect(nextModel, currentModel, "cycle");
|
|
1265
1347
|
return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
|
|
@@ -1270,20 +1352,21 @@ export class AgentSession {
|
|
|
1270
1352
|
/**
|
|
1271
1353
|
* Set thinking level.
|
|
1272
1354
|
* Clamps to model capabilities based on available thinking levels.
|
|
1273
|
-
* Saves to session
|
|
1355
|
+
* Saves the clamped level to the session transcript only if the level actually changes.
|
|
1356
|
+
* Persists the requested level to global defaults only when options.persist is true.
|
|
1274
1357
|
*/
|
|
1275
|
-
setThinkingLevel(level) {
|
|
1358
|
+
setThinkingLevel(level, options = {}) {
|
|
1276
1359
|
const availableLevels = this.getAvailableThinkingLevels();
|
|
1277
1360
|
const effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);
|
|
1278
1361
|
// Only persist if actually changing
|
|
1279
1362
|
const previousLevel = this.agent.state.thinkingLevel;
|
|
1280
1363
|
const isChanging = effectiveLevel !== previousLevel;
|
|
1281
1364
|
this.agent.state.thinkingLevel = effectiveLevel;
|
|
1365
|
+
if (options.persist) {
|
|
1366
|
+
this.settingsManager.setDefaultThinkingLevel(level);
|
|
1367
|
+
}
|
|
1282
1368
|
if (isChanging) {
|
|
1283
1369
|
this.sessionManager.appendThinkingLevelChange(effectiveLevel);
|
|
1284
|
-
if (this.supportsThinking() || effectiveLevel !== "off") {
|
|
1285
|
-
this.settingsManager.setDefaultThinkingLevel(effectiveLevel);
|
|
1286
|
-
}
|
|
1287
1370
|
this._emit({ type: "thinking_level_changed", level: effectiveLevel });
|
|
1288
1371
|
void this._extensionRunner.emit({
|
|
1289
1372
|
type: "thinking_level_select",
|
|
@@ -1296,14 +1379,14 @@ export class AgentSession {
|
|
|
1296
1379
|
* Cycle to next thinking level.
|
|
1297
1380
|
* @returns New level, or undefined if model doesn't support thinking
|
|
1298
1381
|
*/
|
|
1299
|
-
cycleThinkingLevel() {
|
|
1382
|
+
cycleThinkingLevel(options = {}) {
|
|
1300
1383
|
if (!this.supportsThinking())
|
|
1301
1384
|
return undefined;
|
|
1302
1385
|
const levels = this.getAvailableThinkingLevels();
|
|
1303
1386
|
const currentIndex = levels.indexOf(this.thinkingLevel);
|
|
1304
1387
|
const nextIndex = (currentIndex + 1) % levels.length;
|
|
1305
1388
|
const nextLevel = levels[nextIndex];
|
|
1306
|
-
this.setThinkingLevel(nextLevel);
|
|
1389
|
+
this.setThinkingLevel(nextLevel, options);
|
|
1307
1390
|
return nextLevel;
|
|
1308
1391
|
}
|
|
1309
1392
|
/**
|
|
@@ -1312,7 +1395,7 @@ export class AgentSession {
|
|
|
1312
1395
|
*/
|
|
1313
1396
|
getAvailableThinkingLevels() {
|
|
1314
1397
|
if (!this.model)
|
|
1315
|
-
return
|
|
1398
|
+
return [...THINKING_LEVEL_OPTIONS];
|
|
1316
1399
|
return getSupportedThinkingLevels(this.model);
|
|
1317
1400
|
}
|
|
1318
1401
|
/**
|
|
@@ -1321,14 +1404,18 @@ export class AgentSession {
|
|
|
1321
1404
|
supportsThinking() {
|
|
1322
1405
|
return !!this.model?.reasoning;
|
|
1323
1406
|
}
|
|
1324
|
-
_getThinkingLevelForModelSwitch(explicitLevel) {
|
|
1407
|
+
_getThinkingLevelForModelSwitch(targetModel, explicitLevel) {
|
|
1325
1408
|
if (explicitLevel !== undefined) {
|
|
1326
1409
|
return explicitLevel;
|
|
1327
1410
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1411
|
+
// Per-model default takes priority when switching to a model that has one
|
|
1412
|
+
if (targetModel) {
|
|
1413
|
+
const perModel = this.settingsManager.getModelThinkingLevel(targetModel.provider, targetModel.id);
|
|
1414
|
+
if (perModel !== undefined) {
|
|
1415
|
+
return perModel;
|
|
1416
|
+
}
|
|
1330
1417
|
}
|
|
1331
|
-
return this.thinkingLevel;
|
|
1418
|
+
return this.settingsManager.getDefaultThinkingLevel() ?? this.thinkingLevel ?? DEFAULT_THINKING_LEVEL;
|
|
1332
1419
|
}
|
|
1333
1420
|
_clampThinkingLevel(level, _availableLevels) {
|
|
1334
1421
|
return this.model ? clampThinkingLevel(this.model, level) : "off";
|
|
@@ -1359,15 +1446,30 @@ export class AgentSession {
|
|
|
1359
1446
|
// =========================================================================
|
|
1360
1447
|
// Compaction
|
|
1361
1448
|
// =========================================================================
|
|
1449
|
+
/** Generate Pi's built-in compaction summary for manual and automatic compaction. */
|
|
1450
|
+
async _runDefaultCompaction(preparation, requestModel, apiKey, headers, customInstructions, signal, env, reason) {
|
|
1451
|
+
return compact(preparation, requestModel, apiKey, headers, customInstructions, signal, this.thinkingLevel, this.agent.streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }), undefined);
|
|
1452
|
+
}
|
|
1362
1453
|
/**
|
|
1363
1454
|
* Manually compact the session context.
|
|
1364
|
-
*
|
|
1455
|
+
*
|
|
1456
|
+
* This is the manual entry point used by `/compact`, RPC, and extensions. It is
|
|
1457
|
+
* separate from automatic threshold/overflow compaction, which enters through
|
|
1458
|
+
* `_checkCompaction()` and `_runAutoCompaction()`. After preparation and the
|
|
1459
|
+
* `session_before_compact` hook, both paths call the lower-level `compact()`
|
|
1460
|
+
* function imported from `./compaction/index.ts`, unless the hook cancels or
|
|
1461
|
+
* supplies a custom result.
|
|
1462
|
+
*
|
|
1463
|
+
* Aborts the current agent operation first. Manual compaction never retries or
|
|
1464
|
+
* continues the interrupted agent turn.
|
|
1465
|
+
*
|
|
1365
1466
|
* @param customInstructions Optional instructions for the compaction summary
|
|
1366
1467
|
*/
|
|
1367
1468
|
async compact(customInstructions) {
|
|
1368
1469
|
await this.abort();
|
|
1369
1470
|
this._compactionAbortController = new AbortController();
|
|
1370
1471
|
this._emit({ type: "compaction_start", reason: "manual" });
|
|
1472
|
+
let fromExtension = false;
|
|
1371
1473
|
try {
|
|
1372
1474
|
if (!this.model) {
|
|
1373
1475
|
throw new Error(formatNoModelSelectedMessage());
|
|
@@ -1385,7 +1487,6 @@ export class AgentSession {
|
|
|
1385
1487
|
throw new Error("Nothing to compact (session too small)");
|
|
1386
1488
|
}
|
|
1387
1489
|
let extensionCompaction;
|
|
1388
|
-
let fromExtension = false;
|
|
1389
1490
|
if (this._extensionRunner.hasHandlers("session_before_compact")) {
|
|
1390
1491
|
const result = (await this._extensionRunner.emit({
|
|
1391
1492
|
type: "session_before_compact",
|
|
@@ -1418,8 +1519,8 @@ export class AgentSession {
|
|
|
1418
1519
|
details = extensionCompaction.details;
|
|
1419
1520
|
}
|
|
1420
1521
|
else {
|
|
1421
|
-
//
|
|
1422
|
-
const result = await
|
|
1522
|
+
// Shared default summary generator, also used by automatic compaction.
|
|
1523
|
+
const result = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, customInstructions, this._compactionAbortController.signal, env, "manual");
|
|
1423
1524
|
summary = result.summary;
|
|
1424
1525
|
firstKeptEntryId = result.firstKeptEntryId;
|
|
1425
1526
|
tokensBefore = result.tokensBefore;
|
|
@@ -1467,6 +1568,7 @@ export class AgentSession {
|
|
|
1467
1568
|
catch (error) {
|
|
1468
1569
|
const message = error instanceof Error ? error.message : String(error);
|
|
1469
1570
|
const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
|
|
1571
|
+
const errorMessage = aborted ? undefined : `Compaction failed: ${message}`;
|
|
1470
1572
|
this._compactionAbortController = undefined;
|
|
1471
1573
|
this._emit({
|
|
1472
1574
|
type: "compaction_end",
|
|
@@ -1474,7 +1576,14 @@ export class AgentSession {
|
|
|
1474
1576
|
result: undefined,
|
|
1475
1577
|
aborted,
|
|
1476
1578
|
willRetry: false,
|
|
1477
|
-
errorMessage
|
|
1579
|
+
errorMessage,
|
|
1580
|
+
});
|
|
1581
|
+
await this._emitSessionCompactFailed({
|
|
1582
|
+
reason: "manual",
|
|
1583
|
+
errorMessage,
|
|
1584
|
+
aborted,
|
|
1585
|
+
willRetry: false,
|
|
1586
|
+
fromExtension,
|
|
1478
1587
|
});
|
|
1479
1588
|
throw error;
|
|
1480
1589
|
}
|
|
@@ -1496,16 +1605,25 @@ export class AgentSession {
|
|
|
1496
1605
|
this._branchSummaryAbortController?.abort();
|
|
1497
1606
|
}
|
|
1498
1607
|
/**
|
|
1499
|
-
*
|
|
1500
|
-
*
|
|
1608
|
+
* Dispatch automatic compaction after `agent_end` or before prompt submission.
|
|
1609
|
+
* Manual compaction does not call this method; it enters through `compact()`.
|
|
1610
|
+
*
|
|
1611
|
+
* Automatic cases:
|
|
1612
|
+
* 1. Overflow with retry: a context-overflow error or recoverable length stop;
|
|
1613
|
+
* remove the failed assistant message, compact, and retry the turn once.
|
|
1614
|
+
* 2. Overflow without retry: a successful response exceeded the configured
|
|
1615
|
+
* context window; compact but preserve the completed response.
|
|
1616
|
+
* 3. Threshold without retry: valid or estimated context usage crossed the
|
|
1617
|
+
* configured threshold; compact without retrying the completed response.
|
|
1501
1618
|
*
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1619
|
+
* Each case calls `_runAutoCompaction()`. After preparation and the
|
|
1620
|
+
* `session_before_compact` hook, that method calls the lower-level `compact()`
|
|
1621
|
+
* function imported from `./compaction/index.ts`, unless the hook cancels or
|
|
1622
|
+
* supplies a custom result.
|
|
1506
1623
|
*
|
|
1507
1624
|
* @param assistantMessage The assistant message to check
|
|
1508
1625
|
* @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
|
|
1626
|
+
* @returns Whether the post-run loop should call `agent.continue()` for overflow recovery or queued messages
|
|
1509
1627
|
*/
|
|
1510
1628
|
async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
|
|
1511
1629
|
const settings = this.settingsManager.getCompactionSettings();
|
|
@@ -1528,38 +1646,49 @@ export class AgentSession {
|
|
|
1528
1646
|
if (assistantIsFromBeforeCompaction) {
|
|
1529
1647
|
return false;
|
|
1530
1648
|
}
|
|
1531
|
-
//
|
|
1649
|
+
// Automatic cases 1 and 2: context overflow.
|
|
1532
1650
|
// A length stop is recoverable when output ended below the model's original desired limit,
|
|
1533
1651
|
// independent of the configured context size or any context-clamped provider request limit.
|
|
1534
|
-
|
|
1535
|
-
// assistant answer already completed and agent.continue() cannot continue from an assistant.
|
|
1652
|
+
const contextOverflow = sameModel && isContextOverflow(assistantMessage, contextWindow);
|
|
1536
1653
|
const recoverableLength = sameModel && isRecoverableLength(assistantMessage, this.model?.maxTokens ?? 0);
|
|
1537
|
-
if (
|
|
1654
|
+
if (contextOverflow || recoverableLength) {
|
|
1538
1655
|
const willRetry = assistantMessage.stopReason !== "stop";
|
|
1656
|
+
// Case 2: the response completed successfully. Compact, but do not retry because
|
|
1657
|
+
// agent.continue() cannot continue from a completed assistant response.
|
|
1539
1658
|
if (!willRetry) {
|
|
1540
1659
|
return await this._runAutoCompaction("overflow", false);
|
|
1541
1660
|
}
|
|
1542
1661
|
if (this._overflowRecoveryAttempted) {
|
|
1662
|
+
const errorMessage = contextOverflow
|
|
1663
|
+
? "Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model."
|
|
1664
|
+
: "Truncated response recovery failed after one compact-and-retry attempt.";
|
|
1543
1665
|
this._emit({
|
|
1544
1666
|
type: "compaction_end",
|
|
1545
1667
|
reason: "overflow",
|
|
1546
1668
|
result: undefined,
|
|
1547
1669
|
aborted: false,
|
|
1548
1670
|
willRetry: false,
|
|
1549
|
-
errorMessage
|
|
1671
|
+
errorMessage,
|
|
1672
|
+
});
|
|
1673
|
+
await this._emitSessionCompactFailed({
|
|
1674
|
+
reason: "overflow",
|
|
1675
|
+
errorMessage,
|
|
1676
|
+
aborted: false,
|
|
1677
|
+
willRetry: false,
|
|
1678
|
+
fromExtension: false,
|
|
1550
1679
|
});
|
|
1551
1680
|
return false;
|
|
1552
1681
|
}
|
|
1682
|
+
// Case 1: remove the failed or truncated message from agent state, compact, and
|
|
1683
|
+
// retry once. The message remains in session history but is excluded from retry context.
|
|
1553
1684
|
this._overflowRecoveryAttempted = true;
|
|
1554
|
-
// Remove the failed or truncated message from agent state. It remains in session history,
|
|
1555
|
-
// but must not be included in the compact-and-retry context.
|
|
1556
1685
|
const messages = this.agent.state.messages;
|
|
1557
1686
|
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
1558
1687
|
this.agent.state.messages = messages.slice(0, -1);
|
|
1559
1688
|
}
|
|
1560
1689
|
return await this._runAutoCompaction("overflow", willRetry);
|
|
1561
1690
|
}
|
|
1562
|
-
// Case
|
|
1691
|
+
// Case 3: threshold compaction without retry.
|
|
1563
1692
|
// For error messages or all-zero usage messages, estimate from the last valid response.
|
|
1564
1693
|
// This ensures sessions that hit persistent API errors (e.g. 529) or malformed zero-usage
|
|
1565
1694
|
// responses can still compact and do not reset context accounting.
|
|
@@ -1568,16 +1697,18 @@ export class AgentSession {
|
|
|
1568
1697
|
if (assistantMessage.stopReason === "error" || directContextTokens === 0) {
|
|
1569
1698
|
const messages = this.agent.state.messages;
|
|
1570
1699
|
const estimate = estimateContextTokens(messages);
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1700
|
+
// Without provider usage, estimate.tokens is the pure message-size estimate.
|
|
1701
|
+
// Only usage-backed estimates need the stale pre-compaction check.
|
|
1702
|
+
if (estimate.lastUsageIndex !== null) {
|
|
1703
|
+
// Verify the usage source is post-compaction. Kept pre-compaction messages
|
|
1704
|
+
// have stale usage reflecting the old (larger) context and would falsely
|
|
1705
|
+
// trigger compaction right after one just finished.
|
|
1706
|
+
const usageMsg = messages[estimate.lastUsageIndex];
|
|
1707
|
+
if (compactionEntry &&
|
|
1708
|
+
usageMsg.role === "assistant" &&
|
|
1709
|
+
usageMsg.timestamp <= new Date(compactionEntry.timestamp).getTime()) {
|
|
1710
|
+
return false;
|
|
1711
|
+
}
|
|
1581
1712
|
}
|
|
1582
1713
|
contextTokens = estimate.tokens;
|
|
1583
1714
|
}
|
|
@@ -1590,11 +1721,19 @@ export class AgentSession {
|
|
|
1590
1721
|
return false;
|
|
1591
1722
|
}
|
|
1592
1723
|
/**
|
|
1593
|
-
*
|
|
1724
|
+
* Execute threshold or overflow compaction. Manual compaction uses
|
|
1725
|
+
* `AgentSession.compact()` instead. Both paths call the lower-level `compact()`
|
|
1726
|
+
* function imported from `./compaction/index.ts` after preparation and extension
|
|
1727
|
+
* interception.
|
|
1728
|
+
*
|
|
1729
|
+
* @param reason Automatic trigger selected by `_checkCompaction()`
|
|
1730
|
+
* @param willRetry Whether to continue the interrupted turn after overflow compaction
|
|
1731
|
+
* @returns Whether the post-run loop should call `agent.continue()`
|
|
1594
1732
|
*/
|
|
1595
1733
|
async _runAutoCompaction(reason, willRetry) {
|
|
1596
1734
|
const settings = this.settingsManager.getCompactionSettings();
|
|
1597
1735
|
let started = false;
|
|
1736
|
+
let fromExtension = false;
|
|
1598
1737
|
try {
|
|
1599
1738
|
if (!this.model) {
|
|
1600
1739
|
return false;
|
|
@@ -1609,7 +1748,6 @@ export class AgentSession {
|
|
|
1609
1748
|
this._autoCompactionAbortController = new AbortController();
|
|
1610
1749
|
started = true;
|
|
1611
1750
|
let extensionCompaction;
|
|
1612
|
-
let fromExtension = false;
|
|
1613
1751
|
if (this._extensionRunner.hasHandlers("session_before_compact")) {
|
|
1614
1752
|
const extensionResult = (await this._extensionRunner.emit({
|
|
1615
1753
|
type: "session_before_compact",
|
|
@@ -1628,6 +1766,12 @@ export class AgentSession {
|
|
|
1628
1766
|
aborted: true,
|
|
1629
1767
|
willRetry: false,
|
|
1630
1768
|
});
|
|
1769
|
+
await this._emitSessionCompactFailed({
|
|
1770
|
+
reason,
|
|
1771
|
+
aborted: true,
|
|
1772
|
+
willRetry: false,
|
|
1773
|
+
fromExtension: false,
|
|
1774
|
+
});
|
|
1631
1775
|
return false;
|
|
1632
1776
|
}
|
|
1633
1777
|
if (extensionResult?.compaction) {
|
|
@@ -1649,8 +1793,8 @@ export class AgentSession {
|
|
|
1649
1793
|
details = extensionCompaction.details;
|
|
1650
1794
|
}
|
|
1651
1795
|
else {
|
|
1652
|
-
//
|
|
1653
|
-
const compactResult = await
|
|
1796
|
+
// Shared default summary generator, also used by manual compaction.
|
|
1797
|
+
const compactResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, undefined, this._autoCompactionAbortController.signal, env, reason);
|
|
1654
1798
|
summary = compactResult.summary;
|
|
1655
1799
|
firstKeptEntryId = compactResult.firstKeptEntryId;
|
|
1656
1800
|
tokensBefore = compactResult.tokensBefore;
|
|
@@ -1665,6 +1809,12 @@ export class AgentSession {
|
|
|
1665
1809
|
aborted: true,
|
|
1666
1810
|
willRetry: false,
|
|
1667
1811
|
});
|
|
1812
|
+
await this._emitSessionCompactFailed({
|
|
1813
|
+
reason,
|
|
1814
|
+
aborted: true,
|
|
1815
|
+
willRetry: false,
|
|
1816
|
+
fromExtension,
|
|
1817
|
+
});
|
|
1668
1818
|
return false;
|
|
1669
1819
|
}
|
|
1670
1820
|
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
|
|
@@ -1711,15 +1861,23 @@ export class AgentSession {
|
|
|
1711
1861
|
catch (error) {
|
|
1712
1862
|
const errorMessage = error instanceof Error ? error.message : "compaction failed";
|
|
1713
1863
|
if (started) {
|
|
1864
|
+
const formattedErrorMessage = reason === "overflow"
|
|
1865
|
+
? `Context overflow recovery failed: ${errorMessage}`
|
|
1866
|
+
: `Auto-compaction failed: ${errorMessage}`;
|
|
1714
1867
|
this._emit({
|
|
1715
1868
|
type: "compaction_end",
|
|
1716
1869
|
reason,
|
|
1717
1870
|
result: undefined,
|
|
1718
1871
|
aborted: false,
|
|
1719
1872
|
willRetry: false,
|
|
1720
|
-
errorMessage:
|
|
1721
|
-
|
|
1722
|
-
|
|
1873
|
+
errorMessage: formattedErrorMessage,
|
|
1874
|
+
});
|
|
1875
|
+
await this._emitSessionCompactFailed({
|
|
1876
|
+
reason,
|
|
1877
|
+
errorMessage: formattedErrorMessage,
|
|
1878
|
+
aborted: false,
|
|
1879
|
+
willRetry: false,
|
|
1880
|
+
fromExtension,
|
|
1723
1881
|
});
|
|
1724
1882
|
}
|
|
1725
1883
|
return false;
|
|
@@ -2607,29 +2765,7 @@ export class AgentSession {
|
|
|
2607
2765
|
* @returns The resolved output file path.
|
|
2608
2766
|
*/
|
|
2609
2767
|
exportToJsonl(outputPath) {
|
|
2610
|
-
|
|
2611
|
-
const dir = dirname(filePath);
|
|
2612
|
-
if (!existsSync(dir)) {
|
|
2613
|
-
mkdirSync(dir, { recursive: true });
|
|
2614
|
-
}
|
|
2615
|
-
const header = {
|
|
2616
|
-
type: "session",
|
|
2617
|
-
version: CURRENT_SESSION_VERSION,
|
|
2618
|
-
id: this.sessionManager.getSessionId(),
|
|
2619
|
-
timestamp: new Date().toISOString(),
|
|
2620
|
-
cwd: this.sessionManager.getCwd(),
|
|
2621
|
-
};
|
|
2622
|
-
const branchEntries = this.sessionManager.getBranch();
|
|
2623
|
-
const lines = [JSON.stringify(header)];
|
|
2624
|
-
// Re-chain parentIds to form a linear sequence
|
|
2625
|
-
let prevId = null;
|
|
2626
|
-
for (const entry of branchEntries) {
|
|
2627
|
-
const linear = { ...entry, parentId: prevId };
|
|
2628
|
-
lines.push(JSON.stringify(linear));
|
|
2629
|
-
prevId = entry.id;
|
|
2630
|
-
}
|
|
2631
|
-
writeFileSync(filePath, `${lines.join("\n")}\n`);
|
|
2632
|
-
return filePath;
|
|
2768
|
+
return exportSessionToJsonl(this.sessionManager, outputPath);
|
|
2633
2769
|
}
|
|
2634
2770
|
// =========================================================================
|
|
2635
2771
|
// Utilities
|