@royalcat/opencode-dcp-rc 0.0.0-stage → 4.0.1
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/LICENSE +619 -0
- package/README.md +348 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8129 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/commands/compression-targets.d.ts +13 -0
- package/dist/lib/commands/compression-targets.d.ts.map +1 -0
- package/dist/lib/commands/context.d.ts +67 -0
- package/dist/lib/commands/context.d.ts.map +1 -0
- package/dist/lib/commands/decompress.d.ts +12 -0
- package/dist/lib/commands/decompress.d.ts.map +1 -0
- package/dist/lib/commands/help.d.ts +18 -0
- package/dist/lib/commands/help.d.ts.map +1 -0
- package/dist/lib/commands/index.d.ts +8 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/manual.d.ts +23 -0
- package/dist/lib/commands/manual.d.ts.map +1 -0
- package/dist/lib/commands/recompress.d.ts +12 -0
- package/dist/lib/commands/recompress.d.ts.map +1 -0
- package/dist/lib/commands/stats.d.ts +27 -0
- package/dist/lib/commands/stats.d.ts.map +1 -0
- package/dist/lib/commands/sweep.d.ts +23 -0
- package/dist/lib/commands/sweep.d.ts.map +1 -0
- package/dist/lib/compress/index.d.ts +3 -0
- package/dist/lib/compress/index.d.ts.map +1 -0
- package/dist/lib/compress/pipeline.d.ts +29 -0
- package/dist/lib/compress/pipeline.d.ts.map +1 -0
- package/dist/lib/compress/protected-content.d.ts +6 -0
- package/dist/lib/compress/protected-content.d.ts.map +1 -0
- package/dist/lib/compress/rc.d.ts +15 -0
- package/dist/lib/compress/rc.d.ts.map +1 -0
- package/dist/lib/compress/search.d.ts +12 -0
- package/dist/lib/compress/search.d.ts.map +1 -0
- package/dist/lib/compress/state.d.ts +10 -0
- package/dist/lib/compress/state.d.ts.map +1 -0
- package/dist/lib/compress/summary.d.ts +68 -0
- package/dist/lib/compress/summary.d.ts.map +1 -0
- package/dist/lib/compress/timing.d.ts +18 -0
- package/dist/lib/compress/timing.d.ts.map +1 -0
- package/dist/lib/compress/types.d.ts +64 -0
- package/dist/lib/compress/types.d.ts.map +1 -0
- package/dist/lib/compress/usage.d.ts +45 -0
- package/dist/lib/compress/usage.d.ts.map +1 -0
- package/dist/lib/compress-permission.d.ts +4 -0
- package/dist/lib/compress-permission.d.ts.map +1 -0
- package/dist/lib/config.d.ts +83 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/hooks.d.ts +22 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/lib/logger.d.ts +31 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/message-ids.d.ts +20 -0
- package/dist/lib/message-ids.d.ts.map +1 -0
- package/dist/lib/messages/index.d.ts +8 -0
- package/dist/lib/messages/index.d.ts.map +1 -0
- package/dist/lib/messages/inject/inject.d.ts +7 -0
- package/dist/lib/messages/inject/inject.d.ts.map +1 -0
- package/dist/lib/messages/inject/subagent-results.d.ts +4 -0
- package/dist/lib/messages/inject/subagent-results.d.ts.map +1 -0
- package/dist/lib/messages/inject/utils.d.ts +23 -0
- package/dist/lib/messages/inject/utils.d.ts.map +1 -0
- package/dist/lib/messages/prune.d.ts +4 -0
- package/dist/lib/messages/prune.d.ts.map +1 -0
- package/dist/lib/messages/query.d.ts +7 -0
- package/dist/lib/messages/query.d.ts.map +1 -0
- package/dist/lib/messages/reasoning-strip.d.ts +8 -0
- package/dist/lib/messages/reasoning-strip.d.ts.map +1 -0
- package/dist/lib/messages/shape.d.ts +5 -0
- package/dist/lib/messages/shape.d.ts.map +1 -0
- package/dist/lib/messages/sync.d.ts +4 -0
- package/dist/lib/messages/sync.d.ts.map +1 -0
- package/dist/lib/messages/utils.d.ts +27 -0
- package/dist/lib/messages/utils.d.ts.map +1 -0
- package/dist/lib/prompts/compress-rc.d.ts +4 -0
- package/dist/lib/prompts/compress-rc.d.ts.map +1 -0
- package/dist/lib/prompts/context-limit-nudge.d.ts +2 -0
- package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/nudge.d.ts +4 -0
- package/dist/lib/prompts/extensions/nudge.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/system.d.ts +4 -0
- package/dist/lib/prompts/extensions/system.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/tool.d.ts +3 -0
- package/dist/lib/prompts/extensions/tool.d.ts.map +1 -0
- package/dist/lib/prompts/index.d.ts +4 -0
- package/dist/lib/prompts/index.d.ts.map +1 -0
- package/dist/lib/prompts/iteration-nudge.d.ts +2 -0
- package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -0
- package/dist/lib/prompts/store.d.ts +26 -0
- package/dist/lib/prompts/store.d.ts.map +1 -0
- package/dist/lib/prompts/system.d.ts +4 -0
- package/dist/lib/prompts/system.d.ts.map +1 -0
- package/dist/lib/prompts/turn-nudge.d.ts +2 -0
- package/dist/lib/prompts/turn-nudge.d.ts.map +1 -0
- package/dist/lib/protected-patterns.d.ts +6 -0
- package/dist/lib/protected-patterns.d.ts.map +1 -0
- package/dist/lib/state/index.d.ts +5 -0
- package/dist/lib/state/index.d.ts.map +1 -0
- package/dist/lib/state/persistence.d.ts +47 -0
- package/dist/lib/state/persistence.d.ts.map +1 -0
- package/dist/lib/state/state.d.ts +9 -0
- package/dist/lib/state/state.d.ts.map +1 -0
- package/dist/lib/state/tool-cache.d.ts +13 -0
- package/dist/lib/state/tool-cache.d.ts.map +1 -0
- package/dist/lib/state/types.d.ts +105 -0
- package/dist/lib/state/types.d.ts.map +1 -0
- package/dist/lib/state/utils.d.ts +22 -0
- package/dist/lib/state/utils.d.ts.map +1 -0
- package/dist/lib/strategies/deduplication.d.ts +10 -0
- package/dist/lib/strategies/deduplication.d.ts.map +1 -0
- package/dist/lib/strategies/index.d.ts +3 -0
- package/dist/lib/strategies/index.d.ts.map +1 -0
- package/dist/lib/strategies/purge-errors.d.ts +13 -0
- package/dist/lib/strategies/purge-errors.d.ts.map +1 -0
- package/dist/lib/subagents/subagent-results.d.ts +5 -0
- package/dist/lib/subagents/subagent-results.d.ts.map +1 -0
- package/dist/lib/token-utils.d.ts +19 -0
- package/dist/lib/token-utils.d.ts.map +1 -0
- package/dist/lib/tui/dialogs.d.ts +29 -0
- package/dist/lib/tui/dialogs.d.ts.map +1 -0
- package/dist/lib/tui/format.d.ts +4 -0
- package/dist/lib/tui/format.d.ts.map +1 -0
- package/dist/lib/tui/types.d.ts +18 -0
- package/dist/lib/tui/types.d.ts.map +1 -0
- package/dist/lib/tui/ui.d.ts +48 -0
- package/dist/lib/tui/ui.d.ts.map +1 -0
- package/dist/lib/ui/notification.d.ts +17 -0
- package/dist/lib/ui/notification.d.ts.map +1 -0
- package/dist/lib/ui/utils.d.ts +10 -0
- package/dist/lib/ui/utils.d.ts.map +1 -0
- package/dist/lib/v2/index.d.ts +5 -0
- package/dist/lib/v2/index.d.ts.map +1 -0
- package/dist/lib/v2/messages.d.ts +13 -0
- package/dist/lib/v2/messages.d.ts.map +1 -0
- package/dist/lib/v2/rpc.d.ts +75 -0
- package/dist/lib/v2/rpc.d.ts.map +1 -0
- package/dist/lib/v2/theme.d.ts +55 -0
- package/dist/lib/v2/theme.d.ts.map +1 -0
- package/dist/lib/v2/tui.d.ts +4 -0
- package/dist/lib/v2/tui.d.ts.map +1 -0
- package/dist/lib/v2/usage.d.ts +44 -0
- package/dist/lib/v2/usage.d.ts.map +1 -0
- package/dist/tui.d.ts +7 -0
- package/dist/tui.d.ts.map +1 -0
- package/lib/commands/compression-targets.ts +54 -0
- package/lib/commands/context.ts +305 -0
- package/lib/commands/decompress.ts +273 -0
- package/lib/commands/help.ts +76 -0
- package/lib/commands/index.ts +11 -0
- package/lib/commands/manual.ts +126 -0
- package/lib/commands/recompress.ts +222 -0
- package/lib/commands/stats.ts +180 -0
- package/lib/commands/sweep.ts +266 -0
- package/lib/compress/index.ts +2 -0
- package/lib/compress/pipeline.ts +116 -0
- package/lib/compress/protected-content.ts +163 -0
- package/lib/compress/rc.ts +494 -0
- package/lib/compress/search.ts +277 -0
- package/lib/compress/state.ts +272 -0
- package/lib/compress/summary.ts +320 -0
- package/lib/compress/timing.ts +77 -0
- package/lib/compress/types.ts +70 -0
- package/lib/compress/usage.ts +168 -0
- package/lib/compress-permission.ts +9 -0
- package/lib/config.ts +995 -0
- package/lib/hooks.ts +205 -0
- package/lib/logger.ts +226 -0
- package/lib/message-ids.ts +181 -0
- package/lib/messages/index.ts +7 -0
- package/lib/messages/inject/inject.ts +215 -0
- package/lib/messages/inject/subagent-results.ts +84 -0
- package/lib/messages/inject/utils.ts +304 -0
- package/lib/messages/prune.ts +238 -0
- package/lib/messages/query.ts +71 -0
- package/lib/messages/reasoning-strip.ts +40 -0
- package/lib/messages/shape.ts +50 -0
- package/lib/messages/sync.ts +124 -0
- package/lib/messages/utils.ts +190 -0
- package/lib/prompts/compress-rc.ts +25 -0
- package/lib/prompts/context-limit-nudge.ts +18 -0
- package/lib/prompts/extensions/nudge.ts +34 -0
- package/lib/prompts/extensions/system.ts +32 -0
- package/lib/prompts/extensions/tool.ts +21 -0
- package/lib/prompts/index.ts +29 -0
- package/lib/prompts/iteration-nudge.ts +6 -0
- package/lib/prompts/store.ts +452 -0
- package/lib/prompts/system.ts +41 -0
- package/lib/prompts/turn-nudge.ts +10 -0
- package/lib/protected-patterns.ts +159 -0
- package/lib/state/index.ts +4 -0
- package/lib/state/persistence.ts +321 -0
- package/lib/state/state.ts +214 -0
- package/lib/state/tool-cache.ts +99 -0
- package/lib/state/types.ts +117 -0
- package/lib/state/utils.ts +345 -0
- package/lib/strategies/deduplication.ts +125 -0
- package/lib/strategies/index.ts +2 -0
- package/lib/strategies/purge-errors.ts +86 -0
- package/lib/subagents/subagent-results.ts +75 -0
- package/lib/token-utils.ts +164 -0
- package/lib/tui/dialogs.tsx +261 -0
- package/lib/tui/format.ts +25 -0
- package/lib/tui/types.ts +25 -0
- package/lib/tui/ui.tsx +245 -0
- package/lib/ui/notification.ts +347 -0
- package/lib/ui/utils.ts +304 -0
- package/lib/v2/index.ts +448 -0
- package/lib/v2/messages.ts +252 -0
- package/lib/v2/rpc.ts +60 -0
- package/lib/v2/theme.ts +67 -0
- package/lib/v2/tui.tsx +115 -0
- package/lib/v2/usage.ts +423 -0
- package/package.json +91 -5
- package/server.js +1 -0
- package/tui.tsx +7 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type { SessionState, WithParts } from "../state"
|
|
2
|
+
import type { Logger } from "../logger"
|
|
3
|
+
import { isMessageCompacted } from "../state/utils"
|
|
4
|
+
import { createSyntheticUserMessage } from "./utils"
|
|
5
|
+
import { getLastUserMessage } from "./query"
|
|
6
|
+
import type { UserMessage } from "@opencode-ai/sdk/v2"
|
|
7
|
+
import { formatBlockRef } from "../message-ids"
|
|
8
|
+
|
|
9
|
+
const PRUNED_TOOL_OUTPUT_REPLACEMENT =
|
|
10
|
+
"[Output removed to save context - information superseded or no longer needed]"
|
|
11
|
+
const PRUNED_TOOL_ERROR_INPUT_REPLACEMENT = "[input removed due to failed tool call]"
|
|
12
|
+
const PRUNED_QUESTION_INPUT_REPLACEMENT = "[questions removed - see output for user's answers]"
|
|
13
|
+
|
|
14
|
+
export const prune = (
|
|
15
|
+
state: SessionState,
|
|
16
|
+
logger: Logger,
|
|
17
|
+
messages: WithParts[],
|
|
18
|
+
summaryBase?: WithParts,
|
|
19
|
+
): void => {
|
|
20
|
+
filterCompressedRanges(state, logger, messages, summaryBase)
|
|
21
|
+
// pruneFullTool(state, logger, messages)
|
|
22
|
+
pruneToolOutputs(state, logger, messages)
|
|
23
|
+
pruneToolInputs(state, logger, messages)
|
|
24
|
+
pruneToolErrors(state, logger, messages)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const pruneFullTool = (state: SessionState, logger: Logger, messages: WithParts[]): void => {
|
|
28
|
+
const messagesToRemove: string[] = []
|
|
29
|
+
|
|
30
|
+
for (const msg of messages) {
|
|
31
|
+
if (isMessageCompacted(state, msg)) {
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : []
|
|
36
|
+
const partsToRemove: string[] = []
|
|
37
|
+
|
|
38
|
+
for (const part of parts) {
|
|
39
|
+
if (part.type !== "tool") {
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!state.prune.tools.has(part.callID)) {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
if (part.tool !== "edit" && part.tool !== "write") {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
partsToRemove.push(part.callID)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (partsToRemove.length === 0) {
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
msg.parts = parts.filter(
|
|
58
|
+
(part) => part.type !== "tool" || !partsToRemove.includes(part.callID),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if (msg.parts.length === 0) {
|
|
62
|
+
messagesToRemove.push(msg.info.id)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (messagesToRemove.length > 0) {
|
|
67
|
+
const result = messages.filter((msg) => !messagesToRemove.includes(msg.info.id))
|
|
68
|
+
messages.length = 0
|
|
69
|
+
messages.push(...result)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const pruneToolOutputs = (state: SessionState, logger: Logger, messages: WithParts[]): void => {
|
|
74
|
+
for (const msg of messages) {
|
|
75
|
+
if (isMessageCompacted(state, msg)) {
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : []
|
|
80
|
+
for (const part of parts) {
|
|
81
|
+
if (part.type !== "tool") {
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
if (!state.prune.tools.has(part.callID)) {
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
if (part.state.status !== "completed") {
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
if (part.tool === "question" || part.tool === "edit" || part.tool === "write") {
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
part.state.output = PRUNED_TOOL_OUTPUT_REPLACEMENT
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const pruneToolInputs = (state: SessionState, logger: Logger, messages: WithParts[]): void => {
|
|
100
|
+
for (const msg of messages) {
|
|
101
|
+
if (isMessageCompacted(state, msg)) {
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : []
|
|
106
|
+
for (const part of parts) {
|
|
107
|
+
if (part.type !== "tool") {
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!state.prune.tools.has(part.callID)) {
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (part.state.status !== "completed") {
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
if (part.tool !== "question") {
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (part.state.input?.questions !== undefined) {
|
|
122
|
+
part.state.input.questions = PRUNED_QUESTION_INPUT_REPLACEMENT
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const pruneToolErrors = (state: SessionState, logger: Logger, messages: WithParts[]): void => {
|
|
129
|
+
for (const msg of messages) {
|
|
130
|
+
if (isMessageCompacted(state, msg)) {
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : []
|
|
135
|
+
for (const part of parts) {
|
|
136
|
+
if (part.type !== "tool") {
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
if (!state.prune.tools.has(part.callID)) {
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
if (part.state.status !== "error") {
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Prune all string inputs for errored tools
|
|
147
|
+
const input = part.state.input
|
|
148
|
+
if (input && typeof input === "object") {
|
|
149
|
+
for (const key of Object.keys(input)) {
|
|
150
|
+
if (typeof input[key] === "string") {
|
|
151
|
+
input[key] = PRUNED_TOOL_ERROR_INPUT_REPLACEMENT
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const filterCompressedRanges = (
|
|
160
|
+
state: SessionState,
|
|
161
|
+
logger: Logger,
|
|
162
|
+
messages: WithParts[],
|
|
163
|
+
summaryBase?: WithParts,
|
|
164
|
+
): void => {
|
|
165
|
+
if (
|
|
166
|
+
state.prune.messages.byMessageId.size === 0 &&
|
|
167
|
+
state.prune.messages.activeByAnchorMessageId.size === 0
|
|
168
|
+
) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const result: WithParts[] = []
|
|
173
|
+
|
|
174
|
+
for (const msg of messages) {
|
|
175
|
+
const msgId = msg.info.id
|
|
176
|
+
|
|
177
|
+
// Check if there's a summary to inject at this anchor point
|
|
178
|
+
const blockId = state.prune.messages.activeByAnchorMessageId.get(msgId)
|
|
179
|
+
const summary =
|
|
180
|
+
blockId !== undefined ? state.prune.messages.blocksById.get(blockId) : undefined
|
|
181
|
+
if (summary) {
|
|
182
|
+
const rawSummaryContent = (summary as { summary?: unknown }).summary
|
|
183
|
+
if (
|
|
184
|
+
summary.active !== true ||
|
|
185
|
+
typeof rawSummaryContent !== "string" ||
|
|
186
|
+
rawSummaryContent.length === 0
|
|
187
|
+
) {
|
|
188
|
+
logger.warn("Skipping malformed compress summary", {
|
|
189
|
+
anchorMessageId: msgId,
|
|
190
|
+
blockId: (summary as { blockId?: unknown }).blockId,
|
|
191
|
+
})
|
|
192
|
+
} else {
|
|
193
|
+
// Find user message for variant and as base for synthetic message
|
|
194
|
+
const msgIndex = messages.indexOf(msg)
|
|
195
|
+
const userMessage = getLastUserMessage(messages, msgIndex) ?? summaryBase
|
|
196
|
+
|
|
197
|
+
if (userMessage) {
|
|
198
|
+
const userInfo = userMessage.info as UserMessage
|
|
199
|
+
// Persisted summaries can outlive the host's tag format.
|
|
200
|
+
let renderedSummary = rawSummaryContent
|
|
201
|
+
if (state.idFormat === "compact") {
|
|
202
|
+
renderedSummary = renderedSummary.replace(
|
|
203
|
+
/<dcp-message-id>b(\d+)<\/dcp-message-id>\s*$/i,
|
|
204
|
+
(_, id) => formatBlockRef(Number(id), "compact"),
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
const summaryContent = renderedSummary
|
|
208
|
+
const summarySeed = `${summary.blockId}:${summary.anchorMessageId}`
|
|
209
|
+
result.push(
|
|
210
|
+
createSyntheticUserMessage(userMessage, summaryContent, summarySeed),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
logger.info("Injected compress summary", {
|
|
214
|
+
anchorMessageId: msgId,
|
|
215
|
+
summaryLength: summaryContent.length,
|
|
216
|
+
})
|
|
217
|
+
} else {
|
|
218
|
+
logger.warn("No user message found for compress summary", {
|
|
219
|
+
anchorMessageId: msgId,
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Skip messages that are in the prune list
|
|
226
|
+
const pruneEntry = state.prune.messages.byMessageId.get(msgId)
|
|
227
|
+
if (pruneEntry && pruneEntry.activeBlockIds.length > 0) {
|
|
228
|
+
continue
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Normal message, include it
|
|
232
|
+
result.push(msg)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Replace messages array contents
|
|
236
|
+
messages.length = 0
|
|
237
|
+
messages.push(...result)
|
|
238
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { PluginConfig } from "../config"
|
|
2
|
+
import type { WithParts } from "../state"
|
|
3
|
+
import { isMessageWithInfo } from "./shape"
|
|
4
|
+
|
|
5
|
+
export const getLastUserMessage = (
|
|
6
|
+
messages: WithParts[],
|
|
7
|
+
startIndex?: number,
|
|
8
|
+
): WithParts | null => {
|
|
9
|
+
const start = startIndex ?? messages.length - 1
|
|
10
|
+
for (let i = start; i >= 0; i--) {
|
|
11
|
+
const msg = messages[i]
|
|
12
|
+
if (!isMessageWithInfo(msg)) {
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
if (msg.info.role === "user" && !isIgnoredUserMessage(msg)) {
|
|
16
|
+
return msg
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const messageHasCompress = (message: WithParts): boolean => {
|
|
23
|
+
if (!isMessageWithInfo(message)) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (message.info.role !== "assistant") {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const parts = Array.isArray(message.parts) ? message.parts : []
|
|
32
|
+
return parts.some(
|
|
33
|
+
(part) =>
|
|
34
|
+
part.type === "tool" && part.tool === "compress" && part.state?.status === "completed",
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const isIgnoredUserMessage = (message: WithParts): boolean => {
|
|
39
|
+
if (!isMessageWithInfo(message)) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (message.info.role !== "user") {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const parts = Array.isArray(message.parts) ? message.parts : []
|
|
48
|
+
if (parts.length === 0) {
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const part of parts) {
|
|
53
|
+
if (!(part as any).ignored) {
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isProtectedUserMessage(config: PluginConfig, message: WithParts): boolean {
|
|
62
|
+
if (!isMessageWithInfo(message)) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
config.compress.protectUserMessages &&
|
|
68
|
+
message.info.role === "user" &&
|
|
69
|
+
!isIgnoredUserMessage(message)
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { WithParts } from "../state"
|
|
2
|
+
import { getLastUserMessage } from "./query"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mirrors opencode's differentModel handling by preserving part content while
|
|
6
|
+
* dropping provider metadata on assistant parts that came from a different
|
|
7
|
+
* model/provider than the current turn's user message.
|
|
8
|
+
*/
|
|
9
|
+
export function stripStaleMetadata(messages: WithParts[]): void {
|
|
10
|
+
const lastUserMessage = getLastUserMessage(messages)
|
|
11
|
+
if (lastUserMessage?.info.role !== "user") {
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const modelID = lastUserMessage.info.model.modelID
|
|
16
|
+
const providerID = lastUserMessage.info.model.providerID
|
|
17
|
+
|
|
18
|
+
messages.forEach((message) => {
|
|
19
|
+
if (message.info.role !== "assistant") {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (message.info.modelID === modelID && message.info.providerID === providerID) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
message.parts = message.parts.map((part) => {
|
|
28
|
+
if (part.type !== "text" && part.type !== "tool" && part.type !== "reasoning") {
|
|
29
|
+
return part
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!("metadata" in part)) {
|
|
33
|
+
return part
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { metadata: _metadata, ...rest } = part
|
|
37
|
+
return rest
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { WithParts } from "../state"
|
|
2
|
+
|
|
3
|
+
export function isMessageWithInfo(message: unknown): message is WithParts {
|
|
4
|
+
if (!message || typeof message !== "object") {
|
|
5
|
+
return false
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const info = (message as any).info
|
|
9
|
+
const parts = (message as any).parts
|
|
10
|
+
if (!info || typeof info !== "object") {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
typeof info.id === "string" &&
|
|
16
|
+
info.id.length > 0 &&
|
|
17
|
+
typeof info.sessionID === "string" &&
|
|
18
|
+
info.sessionID.length > 0 &&
|
|
19
|
+
(info.role === "user" || info.role === "assistant") &&
|
|
20
|
+
info.time &&
|
|
21
|
+
typeof info.time === "object" &&
|
|
22
|
+
typeof info.time.created === "number" &&
|
|
23
|
+
Array.isArray(parts)
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function filterMessages(messages: unknown): WithParts[] {
|
|
28
|
+
if (!Array.isArray(messages)) {
|
|
29
|
+
return []
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return messages.filter(isMessageWithInfo)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function filterMessagesInPlace(messages: unknown): WithParts[] {
|
|
36
|
+
if (!Array.isArray(messages)) {
|
|
37
|
+
return []
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let writeIndex = 0
|
|
41
|
+
|
|
42
|
+
for (const message of messages) {
|
|
43
|
+
if (isMessageWithInfo(message)) {
|
|
44
|
+
messages[writeIndex++] = message
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
messages.length = writeIndex
|
|
49
|
+
return messages as WithParts[]
|
|
50
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { SessionState, WithParts } from "../state"
|
|
2
|
+
import type { Logger } from "../logger"
|
|
3
|
+
|
|
4
|
+
function sortBlocksByCreation(
|
|
5
|
+
a: { createdAt: number; blockId: number },
|
|
6
|
+
b: { createdAt: number; blockId: number },
|
|
7
|
+
): number {
|
|
8
|
+
const createdAtDiff = a.createdAt - b.createdAt
|
|
9
|
+
if (createdAtDiff !== 0) {
|
|
10
|
+
return createdAtDiff
|
|
11
|
+
}
|
|
12
|
+
return a.blockId - b.blockId
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const syncCompressionBlocks = (
|
|
16
|
+
state: SessionState,
|
|
17
|
+
logger: Logger,
|
|
18
|
+
messages: WithParts[],
|
|
19
|
+
): void => {
|
|
20
|
+
const messagesState = state.prune.messages
|
|
21
|
+
if (!messagesState?.blocksById?.size) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const messageIds = new Set(messages.map((msg) => msg.info.id))
|
|
26
|
+
const previousActiveBlockIds = new Set<number>(
|
|
27
|
+
Array.from(messagesState.blocksById.values())
|
|
28
|
+
.filter((block) => block.active)
|
|
29
|
+
.map((block) => block.blockId),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
messagesState.activeBlockIds.clear()
|
|
33
|
+
messagesState.activeByAnchorMessageId.clear()
|
|
34
|
+
|
|
35
|
+
const now = Date.now()
|
|
36
|
+
const missingOriginBlockIds: number[] = []
|
|
37
|
+
const orderedBlocks = Array.from(messagesState.blocksById.values()).sort(sortBlocksByCreation)
|
|
38
|
+
|
|
39
|
+
for (const block of orderedBlocks) {
|
|
40
|
+
const hasOriginMessage =
|
|
41
|
+
typeof block.compressMessageId === "string" &&
|
|
42
|
+
block.compressMessageId.length > 0 &&
|
|
43
|
+
messageIds.has(block.compressMessageId)
|
|
44
|
+
|
|
45
|
+
if (!hasOriginMessage) {
|
|
46
|
+
block.active = false
|
|
47
|
+
block.deactivatedAt = now
|
|
48
|
+
block.deactivatedByBlockId = undefined
|
|
49
|
+
missingOriginBlockIds.push(block.blockId)
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (block.deactivatedByUser) {
|
|
54
|
+
block.active = false
|
|
55
|
+
if (block.deactivatedAt === undefined) {
|
|
56
|
+
block.deactivatedAt = now
|
|
57
|
+
}
|
|
58
|
+
block.deactivatedByBlockId = undefined
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const consumedBlockId of block.consumedBlockIds) {
|
|
63
|
+
if (!messagesState.activeBlockIds.has(consumedBlockId)) {
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const consumedBlock = messagesState.blocksById.get(consumedBlockId)
|
|
68
|
+
if (consumedBlock) {
|
|
69
|
+
consumedBlock.active = false
|
|
70
|
+
consumedBlock.deactivatedAt = now
|
|
71
|
+
consumedBlock.deactivatedByBlockId = block.blockId
|
|
72
|
+
|
|
73
|
+
const mappedBlockId = messagesState.activeByAnchorMessageId.get(
|
|
74
|
+
consumedBlock.anchorMessageId,
|
|
75
|
+
)
|
|
76
|
+
if (mappedBlockId === consumedBlock.blockId) {
|
|
77
|
+
messagesState.activeByAnchorMessageId.delete(consumedBlock.anchorMessageId)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
messagesState.activeBlockIds.delete(consumedBlockId)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
block.active = true
|
|
85
|
+
block.deactivatedAt = undefined
|
|
86
|
+
block.deactivatedByBlockId = undefined
|
|
87
|
+
messagesState.activeBlockIds.add(block.blockId)
|
|
88
|
+
if (messageIds.has(block.anchorMessageId)) {
|
|
89
|
+
messagesState.activeByAnchorMessageId.set(block.anchorMessageId, block.blockId)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const entry of messagesState.byMessageId.values()) {
|
|
94
|
+
const allBlockIds = Array.isArray(entry.allBlockIds)
|
|
95
|
+
? [...new Set(entry.allBlockIds.filter((id) => Number.isInteger(id) && id > 0))]
|
|
96
|
+
: []
|
|
97
|
+
|
|
98
|
+
entry.allBlockIds = allBlockIds
|
|
99
|
+
entry.activeBlockIds = allBlockIds.filter((id) => messagesState.activeBlockIds.has(id))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const nextActiveBlockIds = messagesState.activeBlockIds
|
|
103
|
+
let deactivatedCount = 0
|
|
104
|
+
let reactivatedCount = 0
|
|
105
|
+
|
|
106
|
+
for (const blockId of previousActiveBlockIds) {
|
|
107
|
+
if (!nextActiveBlockIds.has(blockId)) {
|
|
108
|
+
deactivatedCount++
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const blockId of nextActiveBlockIds) {
|
|
112
|
+
if (!previousActiveBlockIds.has(blockId)) {
|
|
113
|
+
reactivatedCount++
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (missingOriginBlockIds.length > 0 || deactivatedCount > 0 || reactivatedCount > 0) {
|
|
118
|
+
logger.info("Synced compress block state", {
|
|
119
|
+
missingOriginCount: missingOriginBlockIds.length,
|
|
120
|
+
deactivatedCount,
|
|
121
|
+
reactivatedCount,
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
}
|