@bastani/atomic 0.8.26-alpha.6 → 0.8.26-alpha.8
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 +29 -0
- package/README.md +5 -5
- package/dist/builtin/intercom/CHANGELOG.md +12 -0
- package/dist/builtin/intercom/package.json +2 -2
- package/dist/builtin/mcp/CHANGELOG.md +12 -0
- package/dist/builtin/mcp/package.json +3 -3
- package/dist/builtin/subagents/CHANGELOG.md +12 -0
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +9 -9
- package/dist/builtin/subagents/agents/debugger.md +6 -6
- package/dist/builtin/subagents/package.json +4 -4
- package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/builtin/subagents/skills/browser/EXAMPLES.md +151 -0
- package/dist/builtin/subagents/skills/browser/LICENSE.txt +21 -0
- package/dist/builtin/subagents/skills/browser/REFERENCE.md +451 -0
- package/dist/builtin/subagents/skills/browser/SKILL.md +170 -0
- package/dist/builtin/subagents/skills/subagent/SKILL.md +4 -4
- package/dist/builtin/web-access/CHANGELOG.md +12 -0
- package/dist/builtin/web-access/package.json +2 -2
- package/dist/builtin/workflows/CHANGELOG.md +17 -0
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +4 -1
- package/dist/builtin/workflows/builtin/goal.ts +127 -99
- package/dist/builtin/workflows/builtin/open-claude-design.ts +224 -147
- package/dist/builtin/workflows/builtin/ralph.ts +160 -197
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/skills/research-codebase/SKILL.md +1 -1
- package/dist/builtin/workflows/src/extension/wiring.ts +13 -1
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +12 -6
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +2 -2
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +2 -2
- package/dist/builtin/workflows/src/shared/types.ts +3 -3
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +2 -1
- package/dist/core/agent-session.d.ts +33 -6
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +155 -182
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +1 -1
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +6 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +23 -10
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/context-compaction.d.ts +61 -0
- package/dist/core/compaction/context-compaction.d.ts.map +1 -0
- package/dist/core/compaction/context-compaction.js +602 -0
- package/dist/core/compaction/context-compaction.js.map +1 -0
- package/dist/core/compaction/index.d.ts +1 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +1 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +3 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/session-manager.d.ts +41 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +146 -7
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/modes/index.d.ts +1 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +17 -0
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/components/context-compaction-summary-message.d.ts +17 -0
- package/dist/modes/interactive/components/context-compaction-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/context-compaction-summary-message.js +83 -0
- package/dist/modes/interactive/components/context-compaction-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +72 -8
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +13 -8
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +8 -1
- 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 +4 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +14 -3
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/compaction.md +89 -37
- package/docs/custom-provider.md +11 -9
- package/docs/extensions.md +44 -40
- package/docs/index.md +2 -9
- package/docs/json.md +15 -12
- package/docs/packages.md +2 -0
- package/docs/providers.md +4 -1
- package/docs/quickstart.md +5 -12
- package/docs/rpc.md +38 -23
- package/docs/sdk.md +3 -10
- package/docs/session-format.md +26 -13
- package/docs/sessions.md +3 -3
- package/docs/settings.md +2 -2
- package/docs/skills.md +1 -15
- package/docs/termux.md +9 -10
- package/docs/themes.md +2 -2
- package/docs/tmux.md +3 -3
- package/docs/tui.md +19 -32
- package/docs/usage.md +2 -2
- package/docs/workflows.md +44 -2
- package/package.json +4 -12
- package/dist/builtin/subagents/skills/browser-use/SKILL.md +0 -234
- package/dist/builtin/subagents/skills/browser-use/references/cdp-python.md +0 -76
- package/dist/builtin/subagents/skills/browser-use/references/multi-session.md +0 -92
- package/node_modules/@earendil-works/pi-tui/README.md +0 -779
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts +0 -54
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js +0 -632
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/box.d.ts +0 -22
- package/node_modules/@earendil-works/pi-tui/dist/components/box.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/box.js +0 -104
- package/node_modules/@earendil-works/pi-tui/dist/components/box.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/cancellable-loader.d.ts +0 -22
- package/node_modules/@earendil-works/pi-tui/dist/components/cancellable-loader.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/cancellable-loader.js +0 -35
- package/node_modules/@earendil-works/pi-tui/dist/components/cancellable-loader.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +0 -249
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +0 -1857
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/image.d.ts +0 -28
- package/node_modules/@earendil-works/pi-tui/dist/components/image.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/image.js +0 -89
- package/node_modules/@earendil-works/pi-tui/dist/components/image.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/input.d.ts +0 -37
- package/node_modules/@earendil-works/pi-tui/dist/components/input.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js +0 -378
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/loader.d.ts +0 -31
- package/node_modules/@earendil-works/pi-tui/dist/components/loader.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/loader.js +0 -69
- package/node_modules/@earendil-works/pi-tui/dist/components/loader.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts +0 -96
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +0 -644
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/select-list.d.ts +0 -50
- package/node_modules/@earendil-works/pi-tui/dist/components/select-list.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/select-list.js +0 -159
- package/node_modules/@earendil-works/pi-tui/dist/components/select-list.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/settings-list.d.ts +0 -50
- package/node_modules/@earendil-works/pi-tui/dist/components/settings-list.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/settings-list.js +0 -185
- package/node_modules/@earendil-works/pi-tui/dist/components/settings-list.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/spacer.d.ts +0 -12
- package/node_modules/@earendil-works/pi-tui/dist/components/spacer.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/spacer.js +0 -23
- package/node_modules/@earendil-works/pi-tui/dist/components/spacer.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/text.d.ts +0 -19
- package/node_modules/@earendil-works/pi-tui/dist/components/text.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/text.js +0 -89
- package/node_modules/@earendil-works/pi-tui/dist/components/text.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/truncated-text.d.ts +0 -13
- package/node_modules/@earendil-works/pi-tui/dist/components/truncated-text.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/truncated-text.js +0 -51
- package/node_modules/@earendil-works/pi-tui/dist/components/truncated-text.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts +0 -39
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.js +0 -2
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.d.ts +0 -16
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.js +0 -110
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +0 -23
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js +0 -32
- package/node_modules/@earendil-works/pi-tui/dist/index.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.d.ts +0 -193
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.js +0 -174
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/keys.d.ts +0 -184
- package/node_modules/@earendil-works/pi-tui/dist/keys.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/keys.js +0 -1173
- package/node_modules/@earendil-works/pi-tui/dist/keys.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/kill-ring.d.ts +0 -28
- package/node_modules/@earendil-works/pi-tui/dist/kill-ring.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/kill-ring.js +0 -44
- package/node_modules/@earendil-works/pi-tui/dist/kill-ring.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.d.ts +0 -3
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js +0 -53
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.d.ts +0 -50
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.js +0 -361
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts +0 -90
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +0 -366
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +0 -113
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +0 -472
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +0 -227
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +0 -1106
- package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/undo-stack.d.ts +0 -17
- package/node_modules/@earendil-works/pi-tui/dist/undo-stack.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/undo-stack.js +0 -25
- package/node_modules/@earendil-works/pi-tui/dist/undo-stack.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +0 -84
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +0 -1029
- package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.d.ts +0 -25
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js +0 -96
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/node_modules/@earendil-works/pi-tui/package.json +0 -47
- package/node_modules/get-east-asian-width/index.d.ts +0 -60
- package/node_modules/get-east-asian-width/index.js +0 -30
- package/node_modules/get-east-asian-width/license +0 -9
- package/node_modules/get-east-asian-width/lookup-data.js +0 -21
- package/node_modules/get-east-asian-width/lookup.js +0 -138
- package/node_modules/get-east-asian-width/package.json +0 -71
- package/node_modules/get-east-asian-width/readme.md +0 -65
- package/node_modules/get-east-asian-width/utilities.js +0 -24
- package/node_modules/marked/LICENSE.md +0 -44
- package/node_modules/marked/README.md +0 -106
- package/node_modules/marked/bin/main.js +0 -282
- package/node_modules/marked/bin/marked.js +0 -15
- package/node_modules/marked/lib/marked.cjs +0 -2211
- package/node_modules/marked/lib/marked.cjs.map +0 -7
- package/node_modules/marked/lib/marked.d.cts +0 -728
- package/node_modules/marked/lib/marked.d.ts +0 -728
- package/node_modules/marked/lib/marked.esm.js +0 -2189
- package/node_modules/marked/lib/marked.esm.js.map +0 -7
- package/node_modules/marked/lib/marked.umd.js +0 -2213
- package/node_modules/marked/lib/marked.umd.js.map +0 -7
- package/node_modules/marked/man/marked.1 +0 -111
- package/node_modules/marked/man/marked.1.md +0 -92
- package/node_modules/marked/marked.min.js +0 -69
- package/node_modules/marked/package.json +0 -111
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import { completeSimple, StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "../messages.js";
|
|
4
|
+
import { buildContextDeletionFilteredPath, buildContextDeletionFilters, } from "../session-manager.js";
|
|
5
|
+
import { estimateTokens } from "./compaction.js";
|
|
6
|
+
export const CONTEXT_COMPACTION_PROMPT_VERSION = 1;
|
|
7
|
+
const CONTEXT_DELETION_PLAN_TOOL_NAME = "context_deletion_plan";
|
|
8
|
+
const ContextDeletionPlanToolParameters = Type.Object({
|
|
9
|
+
deletions: Type.Array(Type.Object({
|
|
10
|
+
kind: StringEnum(["entry", "content_block"], {
|
|
11
|
+
description: "Delete an entire transcript entry or a single content block within one entry.",
|
|
12
|
+
}),
|
|
13
|
+
entryId: Type.String({ minLength: 1, description: "Stable transcript entry id to delete from." }),
|
|
14
|
+
blockIndex: Type.Optional(Type.Integer({
|
|
15
|
+
minimum: 0,
|
|
16
|
+
description: "Required when kind is content_block; omit when kind is entry.",
|
|
17
|
+
})),
|
|
18
|
+
}, { additionalProperties: false }), { description: "Deletion targets only. Protected entries and recent active context must not be included." }),
|
|
19
|
+
}, { additionalProperties: false });
|
|
20
|
+
const CONTEXT_DELETION_PLAN_TOOL = {
|
|
21
|
+
name: CONTEXT_DELETION_PLAN_TOOL_NAME,
|
|
22
|
+
description: "Emit the final context compaction deletion plan as structured data.",
|
|
23
|
+
parameters: ContextDeletionPlanToolParameters,
|
|
24
|
+
};
|
|
25
|
+
const CONTEXT_COMPACTION_SYSTEM_PROMPT = "You are a context compaction planner for an AI coding assistant transcript. Call the context_deletion_plan tool with deletion targets only.";
|
|
26
|
+
const CONTEXT_COMPACTION_FIXED_PROMPT = `You are a context compaction planner for an AI coding assistant transcript.
|
|
27
|
+
|
|
28
|
+
Your task is deletion-only verbatim compaction.
|
|
29
|
+
|
|
30
|
+
You MUST NOT summarize.
|
|
31
|
+
You MUST NOT paraphrase.
|
|
32
|
+
You MUST NOT generate replacement context.
|
|
33
|
+
You MUST NOT mutate retained transcript objects or content.
|
|
34
|
+
Another step will apply deletions locally. Return only deletion targets by stable ID.
|
|
35
|
+
|
|
36
|
+
What Gets Deleted:
|
|
37
|
+
- Redundant tool outputs: file reads already acted on, grep/search results already processed, passing test output no longer needed.
|
|
38
|
+
- Exploratory dead ends: irrelevant files read, unhelpful or empty searches.
|
|
39
|
+
- Verbose boilerplate: license headers, import blocks the agent isn't modifying, configuration files read for reference.
|
|
40
|
+
- Superseded information: earlier versions of files that have since been edited, old error messages from bugs already fixed.
|
|
41
|
+
|
|
42
|
+
What Survives:
|
|
43
|
+
- Active file paths and line numbers: Any reference the agent might need to navigate.
|
|
44
|
+
- Current error messages: Unresolved bugss and their exact text.
|
|
45
|
+
- Reasoning decisions: Why the agent chose approach A over B. An agent's chain of thought (why it chose this file, what pattern it noticed, what fix it decided on) carries more information-per-token than the raw grep output or file content that informed those decisions.
|
|
46
|
+
- Recent tool calls and their results: The last 3-5 operations.
|
|
47
|
+
- User instructions: The original task and any clarifications.
|
|
48
|
+
|
|
49
|
+
<output_format>
|
|
50
|
+
Call the context_deletion_plan tool exactly once with deletion targets in this shape:
|
|
51
|
+
{ "deletions": [{ "kind": "entry", "entryId": "..." }] }
|
|
52
|
+
|
|
53
|
+
For content-block deletions, use:
|
|
54
|
+
{ "kind": "content_block", "entryId": "...", "blockIndex": 0 }
|
|
55
|
+
|
|
56
|
+
Do not write JSON or prose in a text response. The tool call is the final answer.
|
|
57
|
+
</output_format>`;
|
|
58
|
+
function getMessageFromEntry(entry) {
|
|
59
|
+
if (entry.type === "message") {
|
|
60
|
+
return entry.message;
|
|
61
|
+
}
|
|
62
|
+
if (entry.type === "custom_message") {
|
|
63
|
+
return createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp, entry.excludeFromContext);
|
|
64
|
+
}
|
|
65
|
+
if (entry.type === "branch_summary") {
|
|
66
|
+
return createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
function isExcludedFromLlmContext(message) {
|
|
71
|
+
switch (message.role) {
|
|
72
|
+
case "bashExecution":
|
|
73
|
+
return Boolean(message.excludeFromContext);
|
|
74
|
+
case "custom":
|
|
75
|
+
return message.excludeFromContext === true;
|
|
76
|
+
default:
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function getContextEligibleMessageFromEntry(entry) {
|
|
81
|
+
const message = getMessageFromEntry(entry);
|
|
82
|
+
if (!message || isExcludedFromLlmContext(message))
|
|
83
|
+
return undefined;
|
|
84
|
+
return message;
|
|
85
|
+
}
|
|
86
|
+
function textFromUnknownContent(content) {
|
|
87
|
+
if (typeof content === "string")
|
|
88
|
+
return content;
|
|
89
|
+
if (!Array.isArray(content))
|
|
90
|
+
return JSON.stringify(content);
|
|
91
|
+
return content.map((block) => textFromContentBlock(block)).join("\n");
|
|
92
|
+
}
|
|
93
|
+
function textFromContentBlock(block) {
|
|
94
|
+
if (!block || typeof block !== "object")
|
|
95
|
+
return String(block);
|
|
96
|
+
const record = block;
|
|
97
|
+
if (record.type === "text" && typeof record.text === "string")
|
|
98
|
+
return record.text;
|
|
99
|
+
if (record.type === "thinking" && typeof record.thinking === "string")
|
|
100
|
+
return record.thinking;
|
|
101
|
+
if (record.type === "toolCall") {
|
|
102
|
+
const name = typeof record.name === "string" ? record.name : "tool";
|
|
103
|
+
const id = typeof record.id === "string" ? record.id : "unknown";
|
|
104
|
+
const args = "arguments" in record ? JSON.stringify(record.arguments) : "";
|
|
105
|
+
return `toolCall ${id} ${name} ${args}`.trim();
|
|
106
|
+
}
|
|
107
|
+
if (record.type === "image")
|
|
108
|
+
return "[image]";
|
|
109
|
+
return JSON.stringify(record);
|
|
110
|
+
}
|
|
111
|
+
const IMAGE_BLOCK_CHAR_ESTIMATE = 4800;
|
|
112
|
+
const IMAGE_BLOCK_TOKEN_ESTIMATE = Math.ceil(IMAGE_BLOCK_CHAR_ESTIMATE / 4);
|
|
113
|
+
function estimateTextTokens(text) {
|
|
114
|
+
return Math.max(1, Math.ceil(text.length / 4));
|
|
115
|
+
}
|
|
116
|
+
function estimateContentBlockTokens(block, text) {
|
|
117
|
+
if (block && typeof block === "object" && block.type === "image") {
|
|
118
|
+
return IMAGE_BLOCK_TOKEN_ESTIMATE;
|
|
119
|
+
}
|
|
120
|
+
return estimateTextTokens(text);
|
|
121
|
+
}
|
|
122
|
+
function getToolCallIdFromBlock(block) {
|
|
123
|
+
if (!block || typeof block !== "object")
|
|
124
|
+
return undefined;
|
|
125
|
+
const record = block;
|
|
126
|
+
if (record.type !== "toolCall")
|
|
127
|
+
return undefined;
|
|
128
|
+
return typeof record.id === "string" ? record.id : undefined;
|
|
129
|
+
}
|
|
130
|
+
function getToolResultCallId(message) {
|
|
131
|
+
if (message.role !== "toolResult")
|
|
132
|
+
return undefined;
|
|
133
|
+
const callId = message.toolCallId;
|
|
134
|
+
return typeof callId === "string" ? callId : undefined;
|
|
135
|
+
}
|
|
136
|
+
function contentBlocksForEntry(entryId, message, protectedEntry, existingDeletedBlocks) {
|
|
137
|
+
if (message.role === "compactionSummary") {
|
|
138
|
+
const text = message.summary;
|
|
139
|
+
return [
|
|
140
|
+
{
|
|
141
|
+
entryId,
|
|
142
|
+
blockIndex: 0,
|
|
143
|
+
type: "summary",
|
|
144
|
+
text,
|
|
145
|
+
tokenEstimate: estimateTextTokens(text),
|
|
146
|
+
protected: protectedEntry,
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
const content = message.content;
|
|
151
|
+
if (!Array.isArray(content))
|
|
152
|
+
return [];
|
|
153
|
+
return content
|
|
154
|
+
.map((block, blockIndex) => {
|
|
155
|
+
if (existingDeletedBlocks?.has(blockIndex))
|
|
156
|
+
return undefined;
|
|
157
|
+
const text = textFromContentBlock(block);
|
|
158
|
+
return {
|
|
159
|
+
entryId,
|
|
160
|
+
blockIndex,
|
|
161
|
+
type: block && typeof block === "object" && typeof block.type === "string"
|
|
162
|
+
? (block.type)
|
|
163
|
+
: "unknown",
|
|
164
|
+
text,
|
|
165
|
+
tokenEstimate: estimateContentBlockTokens(block, text),
|
|
166
|
+
protected: protectedEntry,
|
|
167
|
+
toolCallId: getToolCallIdFromBlock(block),
|
|
168
|
+
};
|
|
169
|
+
})
|
|
170
|
+
.filter((block) => block !== undefined);
|
|
171
|
+
}
|
|
172
|
+
function messageText(message) {
|
|
173
|
+
switch (message.role) {
|
|
174
|
+
case "bashExecution":
|
|
175
|
+
return `Ran ${message.command}\n${message.output}`;
|
|
176
|
+
case "branchSummary":
|
|
177
|
+
case "compactionSummary":
|
|
178
|
+
return message.summary;
|
|
179
|
+
case "custom":
|
|
180
|
+
case "toolResult":
|
|
181
|
+
case "user":
|
|
182
|
+
return textFromUnknownContent(message.content);
|
|
183
|
+
case "assistant":
|
|
184
|
+
return textFromUnknownContent(message.content);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function hasAssistantError(message) {
|
|
188
|
+
return message.role === "assistant" && message.stopReason === "error";
|
|
189
|
+
}
|
|
190
|
+
function hasToolResultError(message) {
|
|
191
|
+
return message.role === "toolResult" && message.isError === true;
|
|
192
|
+
}
|
|
193
|
+
function hasFailedBashExecution(message) {
|
|
194
|
+
return message.role === "bashExecution" && typeof message.exitCode === "number" && message.exitCode !== 0;
|
|
195
|
+
}
|
|
196
|
+
function collectLatestSummaryCompactionIndex(pathEntries) {
|
|
197
|
+
for (let i = pathEntries.length - 1; i >= 0; i--) {
|
|
198
|
+
if (pathEntries[i].type === "compaction")
|
|
199
|
+
return i;
|
|
200
|
+
}
|
|
201
|
+
return -1;
|
|
202
|
+
}
|
|
203
|
+
function collectActiveEntryIndices(pathEntries, latestCompactionIndex) {
|
|
204
|
+
if (latestCompactionIndex < 0) {
|
|
205
|
+
return pathEntries.map((_, index) => index);
|
|
206
|
+
}
|
|
207
|
+
const latestCompaction = pathEntries[latestCompactionIndex];
|
|
208
|
+
if (latestCompaction.type !== "compaction")
|
|
209
|
+
return pathEntries.map((_, index) => index);
|
|
210
|
+
const indices = [];
|
|
211
|
+
let foundFirstKept = false;
|
|
212
|
+
for (let i = 0; i < latestCompactionIndex; i++) {
|
|
213
|
+
const entry = pathEntries[i];
|
|
214
|
+
if (entry.id === latestCompaction.firstKeptEntryId) {
|
|
215
|
+
foundFirstKept = true;
|
|
216
|
+
}
|
|
217
|
+
if (foundFirstKept)
|
|
218
|
+
indices.push(i);
|
|
219
|
+
}
|
|
220
|
+
for (let i = latestCompactionIndex + 1; i < pathEntries.length; i++) {
|
|
221
|
+
indices.push(i);
|
|
222
|
+
}
|
|
223
|
+
return indices;
|
|
224
|
+
}
|
|
225
|
+
function isProtectedEntry(entry, message, recentEntryIds) {
|
|
226
|
+
if (recentEntryIds.has(entry.id))
|
|
227
|
+
return true;
|
|
228
|
+
if (message.role === "user")
|
|
229
|
+
return true;
|
|
230
|
+
if (message.role === "custom")
|
|
231
|
+
return true;
|
|
232
|
+
if (message.role === "branchSummary" || message.role === "compactionSummary")
|
|
233
|
+
return true;
|
|
234
|
+
if (hasAssistantError(message) || hasToolResultError(message))
|
|
235
|
+
return true;
|
|
236
|
+
if (hasFailedBashExecution(message))
|
|
237
|
+
return true;
|
|
238
|
+
if (entry.type === "branch_summary")
|
|
239
|
+
return true;
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
export function prepareContextCompaction(pathEntries, settings) {
|
|
243
|
+
if (pathEntries.length === 0)
|
|
244
|
+
return undefined;
|
|
245
|
+
const latestCompactionIndex = collectLatestSummaryCompactionIndex(pathEntries);
|
|
246
|
+
const deletionFilters = buildContextDeletionFilters(pathEntries);
|
|
247
|
+
const filteredPathEntries = buildContextDeletionFilteredPath(pathEntries, deletionFilters);
|
|
248
|
+
const filteredEntryById = new Map(filteredPathEntries.map((entry) => [entry.id, entry]));
|
|
249
|
+
const activeEntryIndices = collectActiveEntryIndices(pathEntries, latestCompactionIndex);
|
|
250
|
+
const messageEntryIds = activeEntryIndices
|
|
251
|
+
.map((index) => filteredEntryById.get(pathEntries[index].id))
|
|
252
|
+
.filter((entry) => entry !== undefined && getContextEligibleMessageFromEntry(entry) !== undefined)
|
|
253
|
+
.map((entry) => entry.id);
|
|
254
|
+
const recentEntryIds = new Set(messageEntryIds.slice(-5));
|
|
255
|
+
const protectedEntryIds = new Set();
|
|
256
|
+
const entries = [];
|
|
257
|
+
if (latestCompactionIndex >= 0) {
|
|
258
|
+
const latestCompaction = pathEntries[latestCompactionIndex];
|
|
259
|
+
if (latestCompaction.type === "compaction") {
|
|
260
|
+
const message = createCompactionSummaryMessage(latestCompaction.summary, latestCompaction.tokensBefore, latestCompaction.timestamp);
|
|
261
|
+
const contentBlocks = contentBlocksForEntry(latestCompaction.id, message, true, undefined);
|
|
262
|
+
protectedEntryIds.add(latestCompaction.id);
|
|
263
|
+
entries.push({
|
|
264
|
+
entryId: latestCompaction.id,
|
|
265
|
+
entryType: latestCompaction.type,
|
|
266
|
+
role: message.role,
|
|
267
|
+
text: messageText(message),
|
|
268
|
+
tokenEstimate: estimateTokens(message),
|
|
269
|
+
protected: true,
|
|
270
|
+
contentBlocks,
|
|
271
|
+
message,
|
|
272
|
+
toolCallIds: [],
|
|
273
|
+
toolResultFor: undefined,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
for (const index of activeEntryIndices) {
|
|
278
|
+
const rawEntry = pathEntries[index];
|
|
279
|
+
const entry = filteredEntryById.get(rawEntry.id);
|
|
280
|
+
if (!entry || entry.type === "context_compaction")
|
|
281
|
+
continue;
|
|
282
|
+
const message = getContextEligibleMessageFromEntry(entry);
|
|
283
|
+
if (!message)
|
|
284
|
+
continue;
|
|
285
|
+
const protectedEntry = isProtectedEntry(entry, message, recentEntryIds);
|
|
286
|
+
if (protectedEntry)
|
|
287
|
+
protectedEntryIds.add(entry.id);
|
|
288
|
+
const rawMessage = getContextEligibleMessageFromEntry(rawEntry) ?? message;
|
|
289
|
+
const contentBlocks = contentBlocksForEntry(entry.id, rawMessage, protectedEntry, deletionFilters.deletedContentBlocks.get(entry.id));
|
|
290
|
+
const toolCallIds = contentBlocks.map((block) => block.toolCallId).filter((id) => id !== undefined);
|
|
291
|
+
const text = contentBlocks.length > 0 ? contentBlocks.map((block) => block.text).join("\n") : messageText(message);
|
|
292
|
+
entries.push({
|
|
293
|
+
entryId: entry.id,
|
|
294
|
+
entryType: entry.type,
|
|
295
|
+
role: message.role,
|
|
296
|
+
text,
|
|
297
|
+
tokenEstimate: estimateTokens(message),
|
|
298
|
+
protected: protectedEntry,
|
|
299
|
+
contentBlocks,
|
|
300
|
+
message,
|
|
301
|
+
toolCallIds,
|
|
302
|
+
toolResultFor: getToolResultCallId(message),
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
if (entries.length < 2)
|
|
306
|
+
return undefined;
|
|
307
|
+
return {
|
|
308
|
+
branchEntries: pathEntries,
|
|
309
|
+
transcript: {
|
|
310
|
+
entries,
|
|
311
|
+
protectedEntryIds: [...protectedEntryIds],
|
|
312
|
+
tokensBefore: entries.reduce((total, entry) => total + entry.tokenEstimate, 0),
|
|
313
|
+
settings,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function targetKey(target) {
|
|
318
|
+
return target.kind === "entry" ? `entry:${target.entryId}` : `content_block:${target.entryId}:${target.blockIndex}`;
|
|
319
|
+
}
|
|
320
|
+
function rawTargetKey(target) {
|
|
321
|
+
return target.kind === "entry" ? `entry:${target.entryId}` : `content_block:${target.entryId}:${target.blockIndex}`;
|
|
322
|
+
}
|
|
323
|
+
function normalizeRawTarget(target) {
|
|
324
|
+
if (target.kind === "entry")
|
|
325
|
+
return { kind: "entry", entryId: target.entryId };
|
|
326
|
+
return { kind: "content_block", entryId: target.entryId, blockIndex: target.blockIndex };
|
|
327
|
+
}
|
|
328
|
+
function getDeletedEntryIds(targets) {
|
|
329
|
+
return new Set(targets.filter((target) => target.kind === "entry").map((target) => target.entryId));
|
|
330
|
+
}
|
|
331
|
+
function getDeletedContentBlocks(targets) {
|
|
332
|
+
const blocksByEntry = new Map();
|
|
333
|
+
for (const target of targets) {
|
|
334
|
+
if (target.kind !== "content_block")
|
|
335
|
+
continue;
|
|
336
|
+
const blocks = blocksByEntry.get(target.entryId) ?? new Set();
|
|
337
|
+
blocks.add(target.blockIndex);
|
|
338
|
+
blocksByEntry.set(target.entryId, blocks);
|
|
339
|
+
}
|
|
340
|
+
return blocksByEntry;
|
|
341
|
+
}
|
|
342
|
+
function isToolCallBlockDeleted(entry, callId, deletedEntryIds, deletedContentBlocks) {
|
|
343
|
+
if (deletedEntryIds.has(entry.entryId))
|
|
344
|
+
return true;
|
|
345
|
+
const deletedBlocks = deletedContentBlocks.get(entry.entryId);
|
|
346
|
+
if (!deletedBlocks)
|
|
347
|
+
return false;
|
|
348
|
+
return entry.contentBlocks.some((block) => block.toolCallId === callId && deletedBlocks.has(block.blockIndex));
|
|
349
|
+
}
|
|
350
|
+
function validateToolDependencies(transcript, targets) {
|
|
351
|
+
const deletedEntryIds = getDeletedEntryIds(targets);
|
|
352
|
+
const deletedContentBlocks = getDeletedContentBlocks(targets);
|
|
353
|
+
const callEntries = new Map();
|
|
354
|
+
const resultEntries = new Map();
|
|
355
|
+
for (const entry of transcript.entries) {
|
|
356
|
+
for (const callId of entry.toolCallIds) {
|
|
357
|
+
callEntries.set(callId, entry);
|
|
358
|
+
}
|
|
359
|
+
if (entry.toolResultFor) {
|
|
360
|
+
const results = resultEntries.get(entry.toolResultFor) ?? [];
|
|
361
|
+
results.push(entry);
|
|
362
|
+
resultEntries.set(entry.toolResultFor, results);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
for (const [callId, callEntry] of callEntries) {
|
|
366
|
+
const callDeleted = isToolCallBlockDeleted(callEntry, callId, deletedEntryIds, deletedContentBlocks);
|
|
367
|
+
const results = resultEntries.get(callId) ?? [];
|
|
368
|
+
if (callDeleted) {
|
|
369
|
+
const danglingResult = results.find((entry) => !deletedEntryIds.has(entry.entryId));
|
|
370
|
+
if (danglingResult) {
|
|
371
|
+
throw new Error(`Deleting tool call ${callId} would leave tool result entry ${danglingResult.entryId} orphaned`);
|
|
372
|
+
}
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const deletedResult = results.find((entry) => deletedEntryIds.has(entry.entryId));
|
|
376
|
+
if (deletedResult) {
|
|
377
|
+
throw new Error(`Deleting tool result entry ${deletedResult.entryId} would leave tool call ${callId} dangling`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function computeContextCompactionStats(transcript, targets) {
|
|
382
|
+
const entryById = new Map(transcript.entries.map((entry) => [entry.entryId, entry]));
|
|
383
|
+
const deletedEntryIds = getDeletedEntryIds(targets);
|
|
384
|
+
let deletedTokens = 0;
|
|
385
|
+
let objectsDeleted = 0;
|
|
386
|
+
for (const entryId of deletedEntryIds) {
|
|
387
|
+
const entry = entryById.get(entryId);
|
|
388
|
+
if (!entry)
|
|
389
|
+
continue;
|
|
390
|
+
deletedTokens += entry.tokenEstimate;
|
|
391
|
+
objectsDeleted += 1 + entry.contentBlocks.length;
|
|
392
|
+
}
|
|
393
|
+
for (const target of targets) {
|
|
394
|
+
if (target.kind !== "content_block" || deletedEntryIds.has(target.entryId))
|
|
395
|
+
continue;
|
|
396
|
+
const entry = entryById.get(target.entryId);
|
|
397
|
+
if (!entry)
|
|
398
|
+
continue;
|
|
399
|
+
const block = entry.contentBlocks.find((item) => item.blockIndex === target.blockIndex);
|
|
400
|
+
if (!block)
|
|
401
|
+
continue;
|
|
402
|
+
deletedTokens += block.tokenEstimate;
|
|
403
|
+
objectsDeleted += 1;
|
|
404
|
+
}
|
|
405
|
+
const objectsBefore = transcript.entries.length + transcript.entries.reduce((total, entry) => total + entry.contentBlocks.length, 0);
|
|
406
|
+
const tokensBefore = transcript.tokensBefore;
|
|
407
|
+
const tokensAfter = Math.max(0, tokensBefore - deletedTokens);
|
|
408
|
+
const percentReduction = tokensBefore > 0 ? Math.round(((tokensBefore - tokensAfter) / tokensBefore) * 1000) / 10 : 0;
|
|
409
|
+
return {
|
|
410
|
+
objectsBefore,
|
|
411
|
+
objectsAfter: Math.max(0, objectsBefore - objectsDeleted),
|
|
412
|
+
objectsDeleted,
|
|
413
|
+
tokensBefore,
|
|
414
|
+
tokensAfter,
|
|
415
|
+
percentReduction,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
export function validateContextDeletionPlan(plan, transcript) {
|
|
419
|
+
if (!plan || typeof plan !== "object" || !Array.isArray(plan.deletions)) {
|
|
420
|
+
throw new Error("Context deletion plan must be an object with a deletions array");
|
|
421
|
+
}
|
|
422
|
+
const entryById = new Map(transcript.entries.map((entry) => [entry.entryId, entry]));
|
|
423
|
+
const seen = new Set();
|
|
424
|
+
const deletedEntryIds = new Set();
|
|
425
|
+
const deletedTargets = [];
|
|
426
|
+
for (const deletion of plan.deletions) {
|
|
427
|
+
if (!deletion || typeof deletion !== "object") {
|
|
428
|
+
throw new Error("Deletion target must be an object");
|
|
429
|
+
}
|
|
430
|
+
if (deletion.kind !== "entry" && deletion.kind !== "content_block") {
|
|
431
|
+
throw new Error(`Unsupported deletion target kind: ${String(deletion.kind)}`);
|
|
432
|
+
}
|
|
433
|
+
if (typeof deletion.entryId !== "string" || deletion.entryId.length === 0) {
|
|
434
|
+
throw new Error("Deletion target entryId must be a non-empty string");
|
|
435
|
+
}
|
|
436
|
+
const entry = entryById.get(deletion.entryId);
|
|
437
|
+
if (!entry) {
|
|
438
|
+
throw new Error(`Unknown deletion target entryId: ${deletion.entryId}`);
|
|
439
|
+
}
|
|
440
|
+
if (entry.protected) {
|
|
441
|
+
throw new Error(`Deletion target ${deletion.entryId} is protected`);
|
|
442
|
+
}
|
|
443
|
+
if (deletion.kind === "content_block") {
|
|
444
|
+
if (!Number.isInteger(deletion.blockIndex) || deletion.blockIndex === undefined || deletion.blockIndex < 0) {
|
|
445
|
+
throw new Error(`Invalid content block index for entry ${deletion.entryId}`);
|
|
446
|
+
}
|
|
447
|
+
const block = entry.contentBlocks.find((item) => item.blockIndex === deletion.blockIndex);
|
|
448
|
+
if (!block) {
|
|
449
|
+
throw new Error(`Unknown content block ${deletion.blockIndex} for entry ${deletion.entryId}`);
|
|
450
|
+
}
|
|
451
|
+
if (block.protected) {
|
|
452
|
+
throw new Error(`Content block ${deletion.entryId}:${deletion.blockIndex} is protected`);
|
|
453
|
+
}
|
|
454
|
+
if (entry.contentBlocks.length <= 1) {
|
|
455
|
+
throw new Error(`Deleting the only content block of ${deletion.entryId} must be an entry deletion`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const key = rawTargetKey(deletion);
|
|
459
|
+
if (seen.has(key)) {
|
|
460
|
+
throw new Error(`Duplicate deletion target: ${key}`);
|
|
461
|
+
}
|
|
462
|
+
seen.add(key);
|
|
463
|
+
const normalized = normalizeRawTarget(deletion);
|
|
464
|
+
deletedTargets.push(normalized);
|
|
465
|
+
if (normalized.kind === "entry")
|
|
466
|
+
deletedEntryIds.add(normalized.entryId);
|
|
467
|
+
}
|
|
468
|
+
for (const target of deletedTargets) {
|
|
469
|
+
if (target.kind === "content_block" && deletedEntryIds.has(target.entryId)) {
|
|
470
|
+
throw new Error(`Deletion target ${targetKey(target)} overlaps with entry deletion`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const deletedContentBlocks = getDeletedContentBlocks(deletedTargets);
|
|
474
|
+
for (const [entryId, blockIndexes] of deletedContentBlocks) {
|
|
475
|
+
const entry = entryById.get(entryId);
|
|
476
|
+
if (entry?.contentBlocks.every((block) => blockIndexes.has(block.blockIndex))) {
|
|
477
|
+
throw new Error(`Content-block deletions for ${entryId} would remove every content block`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
validateToolDependencies(transcript, deletedTargets);
|
|
481
|
+
const remainingEntries = transcript.entries.filter((entry) => !deletedEntryIds.has(entry.entryId));
|
|
482
|
+
if (remainingEntries.length === 0) {
|
|
483
|
+
throw new Error("Deletion plan would remove all context entries");
|
|
484
|
+
}
|
|
485
|
+
const hasTaskBearingContext = remainingEntries.some((entry) => entry.role === "user" || (entry.role === "compactionSummary" && entry.protected));
|
|
486
|
+
if (!hasTaskBearingContext) {
|
|
487
|
+
throw new Error("Deletion plan would leave no user task in context");
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
deletedTargets,
|
|
491
|
+
protectedEntryIds: [...transcript.protectedEntryIds],
|
|
492
|
+
stats: computeContextCompactionStats(transcript, deletedTargets),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function stripJsonFence(text) {
|
|
496
|
+
const trimmed = text.trim();
|
|
497
|
+
if (!trimmed.startsWith("```") || !trimmed.endsWith("```"))
|
|
498
|
+
return trimmed;
|
|
499
|
+
const firstLineEnd = trimmed.indexOf("\n");
|
|
500
|
+
if (firstLineEnd < 0)
|
|
501
|
+
return trimmed;
|
|
502
|
+
const fenceInfo = trimmed.slice(3, firstLineEnd).trim().toLowerCase();
|
|
503
|
+
if (fenceInfo !== "" && fenceInfo !== "json")
|
|
504
|
+
return trimmed;
|
|
505
|
+
return trimmed.slice(firstLineEnd + 1, -3).trim();
|
|
506
|
+
}
|
|
507
|
+
function rawContextDeletionPlanFromObject(value, source) {
|
|
508
|
+
if (!value || typeof value !== "object" || !Array.isArray(value.deletions)) {
|
|
509
|
+
throw new Error(`${source} must contain a deletions array`);
|
|
510
|
+
}
|
|
511
|
+
return value;
|
|
512
|
+
}
|
|
513
|
+
export function parseContextDeletionPlan(text) {
|
|
514
|
+
const stripped = stripJsonFence(text);
|
|
515
|
+
let parsed;
|
|
516
|
+
try {
|
|
517
|
+
parsed = JSON.parse(stripped);
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
throw new Error(`Failed to parse context deletion plan JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
521
|
+
}
|
|
522
|
+
return rawContextDeletionPlanFromObject(parsed, "Context deletion plan JSON");
|
|
523
|
+
}
|
|
524
|
+
function isContextDeletionPlanToolCall(content) {
|
|
525
|
+
return content.type === "toolCall" && content.name === CONTEXT_DELETION_PLAN_TOOL_NAME;
|
|
526
|
+
}
|
|
527
|
+
function textContentFromResponse(response) {
|
|
528
|
+
return response.content
|
|
529
|
+
.filter((content) => content.type === "text")
|
|
530
|
+
.map((content) => content.text)
|
|
531
|
+
.join("\n");
|
|
532
|
+
}
|
|
533
|
+
export function parseContextDeletionPlanResponse(response) {
|
|
534
|
+
const toolCalls = response.content.filter(isContextDeletionPlanToolCall);
|
|
535
|
+
if (toolCalls.length > 1) {
|
|
536
|
+
throw new Error(`Context compaction planner called ${CONTEXT_DELETION_PLAN_TOOL_NAME} more than once`);
|
|
537
|
+
}
|
|
538
|
+
const toolCall = toolCalls[0];
|
|
539
|
+
if (toolCall) {
|
|
540
|
+
return rawContextDeletionPlanFromObject(toolCall.arguments, `${CONTEXT_DELETION_PLAN_TOOL_NAME} arguments`);
|
|
541
|
+
}
|
|
542
|
+
const textContent = textContentFromResponse(response);
|
|
543
|
+
if (textContent.trim().length === 0) {
|
|
544
|
+
throw new Error(`Context compaction planner did not call ${CONTEXT_DELETION_PLAN_TOOL_NAME}`);
|
|
545
|
+
}
|
|
546
|
+
return parseContextDeletionPlan(textContent);
|
|
547
|
+
}
|
|
548
|
+
function truncateForPrompt(text, maxChars) {
|
|
549
|
+
if (text.length <= maxChars)
|
|
550
|
+
return text;
|
|
551
|
+
return `${text.slice(0, maxChars)}\n[... ${text.length - maxChars} more characters omitted from planner prompt]`;
|
|
552
|
+
}
|
|
553
|
+
function plannerTranscriptPayload(transcript) {
|
|
554
|
+
return transcript.entries
|
|
555
|
+
.filter((entry) => !isExcludedFromLlmContext(entry.message))
|
|
556
|
+
.map((entry) => ({
|
|
557
|
+
entryId: entry.entryId,
|
|
558
|
+
role: entry.role,
|
|
559
|
+
protected: entry.protected,
|
|
560
|
+
tokenEstimate: entry.tokenEstimate,
|
|
561
|
+
toolCallIds: entry.toolCallIds,
|
|
562
|
+
toolResultFor: entry.toolResultFor,
|
|
563
|
+
contentBlocks: entry.contentBlocks.map((block) => ({
|
|
564
|
+
blockIndex: block.blockIndex,
|
|
565
|
+
type: block.type,
|
|
566
|
+
protected: block.protected,
|
|
567
|
+
toolCallId: block.toolCallId,
|
|
568
|
+
text: truncateForPrompt(block.text, 2000),
|
|
569
|
+
})),
|
|
570
|
+
text: truncateForPrompt(entry.text, 4000),
|
|
571
|
+
}));
|
|
572
|
+
}
|
|
573
|
+
export function buildContextCompactionPrompt(transcript) {
|
|
574
|
+
return `${CONTEXT_COMPACTION_FIXED_PROMPT}\n\n<transcript-json>\n${JSON.stringify(plannerTranscriptPayload(transcript), null, 2)}\n</transcript-json>`;
|
|
575
|
+
}
|
|
576
|
+
export async function planContextDeletions(transcript, model, apiKey, headers, signal, thinkingLevel) {
|
|
577
|
+
const maxTokens = Math.min(4096, model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY);
|
|
578
|
+
const messages = [
|
|
579
|
+
{
|
|
580
|
+
role: "user",
|
|
581
|
+
content: [{ type: "text", text: buildContextCompactionPrompt(transcript) }],
|
|
582
|
+
timestamp: Date.now(),
|
|
583
|
+
},
|
|
584
|
+
];
|
|
585
|
+
const options = model.reasoning && thinkingLevel && thinkingLevel !== "off"
|
|
586
|
+
? { maxTokens, signal, apiKey, headers, reasoning: thinkingLevel }
|
|
587
|
+
: { maxTokens, signal, apiKey, headers };
|
|
588
|
+
const response = await completeSimple(model, { systemPrompt: CONTEXT_COMPACTION_SYSTEM_PROMPT, messages, tools: [CONTEXT_DELETION_PLAN_TOOL] }, options);
|
|
589
|
+
if (response.stopReason === "error") {
|
|
590
|
+
throw new Error(`Context compaction planning failed: ${response.errorMessage || "Unknown error"}`);
|
|
591
|
+
}
|
|
592
|
+
return parseContextDeletionPlanResponse(response);
|
|
593
|
+
}
|
|
594
|
+
export async function contextCompact(preparation, model, apiKey, headers, signal, thinkingLevel) {
|
|
595
|
+
const plan = await planContextDeletions(preparation.transcript, model, apiKey, headers, signal, thinkingLevel);
|
|
596
|
+
const validated = validateContextDeletionPlan(plan, preparation.transcript);
|
|
597
|
+
if (validated.deletedTargets.length === 0) {
|
|
598
|
+
throw new Error("No safe context deletions proposed");
|
|
599
|
+
}
|
|
600
|
+
return validated;
|
|
601
|
+
}
|
|
602
|
+
//# sourceMappingURL=context-compaction.js.map
|