@tarquinen/opencode-dcp 3.2.5-beta0 → 3.2.7-beta0
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/dist/lib/token-utils.js +2 -2
- package/dist/lib/token-utils.js.map +1 -1
- package/index.ts +141 -0
- package/lib/analysis/tokens.ts +225 -0
- package/lib/auth.ts +37 -0
- package/lib/commands/compression-targets.ts +137 -0
- package/lib/commands/context.ts +132 -0
- package/lib/commands/decompress.ts +275 -0
- package/lib/commands/help.ts +76 -0
- package/lib/commands/index.ts +11 -0
- package/lib/commands/manual.ts +125 -0
- package/lib/commands/recompress.ts +224 -0
- package/lib/commands/stats.ts +148 -0
- package/lib/commands/sweep.ts +268 -0
- package/lib/compress/index.ts +3 -0
- package/lib/compress/message-utils.ts +250 -0
- package/lib/compress/message.ts +137 -0
- package/lib/compress/pipeline.ts +106 -0
- package/lib/compress/protected-content.ts +154 -0
- package/lib/compress/range-utils.ts +308 -0
- package/lib/compress/range.ts +180 -0
- package/lib/compress/search.ts +267 -0
- package/lib/compress/state.ts +268 -0
- package/lib/compress/timing.ts +77 -0
- package/lib/compress/types.ts +108 -0
- package/lib/compress-permission.ts +25 -0
- package/lib/config.ts +1071 -0
- package/lib/hooks.ts +378 -0
- package/lib/host-permissions.ts +101 -0
- package/lib/logger.ts +235 -0
- package/lib/message-ids.ts +172 -0
- package/lib/messages/index.ts +8 -0
- package/lib/messages/inject/inject.ts +215 -0
- package/lib/messages/inject/subagent-results.ts +82 -0
- package/lib/messages/inject/utils.ts +374 -0
- package/lib/messages/priority.ts +102 -0
- package/lib/messages/prune.ts +238 -0
- package/lib/messages/query.ts +56 -0
- package/lib/messages/reasoning-strip.ts +40 -0
- package/lib/messages/sync.ts +124 -0
- package/lib/messages/utils.ts +187 -0
- package/lib/prompts/compress-message.ts +42 -0
- package/lib/prompts/compress-range.ts +60 -0
- package/lib/prompts/context-limit-nudge.ts +18 -0
- package/lib/prompts/extensions/nudge.ts +43 -0
- package/lib/prompts/extensions/system.ts +32 -0
- package/lib/prompts/extensions/tool.ts +35 -0
- package/lib/prompts/index.ts +29 -0
- package/lib/prompts/iteration-nudge.ts +6 -0
- package/lib/prompts/store.ts +467 -0
- package/lib/prompts/system.ts +33 -0
- package/lib/prompts/turn-nudge.ts +10 -0
- package/lib/protected-patterns.ts +128 -0
- package/lib/state/index.ts +4 -0
- package/lib/state/persistence.ts +256 -0
- package/lib/state/state.ts +190 -0
- package/lib/state/tool-cache.ts +98 -0
- package/lib/state/types.ts +112 -0
- package/lib/state/utils.ts +334 -0
- package/lib/strategies/deduplication.ts +127 -0
- package/lib/strategies/index.ts +2 -0
- package/lib/strategies/purge-errors.ts +88 -0
- package/lib/subagents/subagent-results.ts +74 -0
- package/lib/token-utils.ts +162 -0
- package/lib/ui/notification.ts +346 -0
- package/lib/ui/utils.ts +287 -0
- package/package.json +12 -3
- package/tui/data/context.ts +177 -0
- package/tui/index.tsx +34 -0
- package/tui/routes/summary.tsx +175 -0
- package/tui/shared/names.ts +9 -0
- package/tui/shared/theme.ts +58 -0
- package/tui/shared/types.ts +38 -0
- package/tui/slots/sidebar-content.tsx +502 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Logger } from "../../logger"
|
|
2
|
+
import type { SessionState, WithParts } from "../../state"
|
|
3
|
+
import {
|
|
4
|
+
buildSubagentResultText,
|
|
5
|
+
getSubAgentId,
|
|
6
|
+
mergeSubagentResult,
|
|
7
|
+
} from "../../subagents/subagent-results"
|
|
8
|
+
import { stripHallucinationsFromString } from "../utils"
|
|
9
|
+
|
|
10
|
+
async function fetchSubAgentMessages(client: any, sessionId: string): Promise<WithParts[]> {
|
|
11
|
+
const response = await client.session.messages({
|
|
12
|
+
path: { id: sessionId },
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const payload = (response?.data || response) as WithParts[]
|
|
16
|
+
return Array.isArray(payload) ? payload : []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const injectExtendedSubAgentResults = async (
|
|
20
|
+
client: any,
|
|
21
|
+
state: SessionState,
|
|
22
|
+
logger: Logger,
|
|
23
|
+
messages: WithParts[],
|
|
24
|
+
allowSubAgents: boolean,
|
|
25
|
+
): Promise<void> => {
|
|
26
|
+
if (!allowSubAgents) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const message of messages) {
|
|
31
|
+
const parts = Array.isArray(message.parts) ? message.parts : []
|
|
32
|
+
|
|
33
|
+
for (const part of parts) {
|
|
34
|
+
if (part.type !== "tool" || part.tool !== "task" || !part.callID) {
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
if (state.prune.tools.has(part.callID)) {
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
if (part.state?.status !== "completed" || typeof part.state.output !== "string") {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cachedResult = state.subAgentResultCache.get(part.callID)
|
|
45
|
+
if (cachedResult !== undefined) {
|
|
46
|
+
if (cachedResult) {
|
|
47
|
+
part.state.output = stripHallucinationsFromString(
|
|
48
|
+
mergeSubagentResult(part.state.output, cachedResult),
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const subAgentSessionId = getSubAgentId(part)
|
|
55
|
+
if (!subAgentSessionId) {
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let subAgentMessages: WithParts[] = []
|
|
60
|
+
try {
|
|
61
|
+
subAgentMessages = await fetchSubAgentMessages(client, subAgentSessionId)
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logger.warn("Failed to fetch subagent session for output expansion", {
|
|
64
|
+
subAgentSessionId,
|
|
65
|
+
callID: part.callID,
|
|
66
|
+
error: error instanceof Error ? error.message : String(error),
|
|
67
|
+
})
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const subAgentResultText = buildSubagentResultText(subAgentMessages)
|
|
72
|
+
if (!subAgentResultText) {
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
state.subAgentResultCache.set(part.callID, subAgentResultText)
|
|
77
|
+
part.state.output = stripHallucinationsFromString(
|
|
78
|
+
mergeSubagentResult(part.state.output, subAgentResultText),
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import type { SessionState, WithParts } from "../../state"
|
|
2
|
+
import type { PluginConfig } from "../../config"
|
|
3
|
+
import {
|
|
4
|
+
appendGuidanceToDcpTag,
|
|
5
|
+
buildCompressedBlockGuidance,
|
|
6
|
+
renderMessagePriorityGuidance,
|
|
7
|
+
} from "../../prompts/extensions/nudge"
|
|
8
|
+
import type { RuntimePrompts } from "../../prompts/store"
|
|
9
|
+
import type { UserMessage } from "@opencode-ai/sdk/v2"
|
|
10
|
+
import {
|
|
11
|
+
type CompressionPriorityMap,
|
|
12
|
+
type MessagePriority,
|
|
13
|
+
listPriorityRefsBeforeIndex,
|
|
14
|
+
} from "../priority"
|
|
15
|
+
import {
|
|
16
|
+
appendToTextPart,
|
|
17
|
+
appendToLastTextPart,
|
|
18
|
+
createSyntheticTextPart,
|
|
19
|
+
hasContent,
|
|
20
|
+
} from "../utils"
|
|
21
|
+
import { getLastUserMessage, isIgnoredUserMessage } from "../query"
|
|
22
|
+
import { getCurrentTokenUsage } from "../../token-utils"
|
|
23
|
+
import { getActiveSummaryTokenUsage } from "../../state/utils"
|
|
24
|
+
|
|
25
|
+
const MESSAGE_MODE_NUDGE_PRIORITY: MessagePriority = "high"
|
|
26
|
+
|
|
27
|
+
export interface LastUserModelContext {
|
|
28
|
+
providerId: string | undefined
|
|
29
|
+
modelId: string | undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface LastNonIgnoredMessage {
|
|
33
|
+
message: WithParts
|
|
34
|
+
index: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getNudgeFrequency(config: PluginConfig): number {
|
|
38
|
+
return Math.max(1, Math.floor(config.compress.nudgeFrequency || 1))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getIterationNudgeThreshold(config: PluginConfig): number {
|
|
42
|
+
return Math.max(1, Math.floor(config.compress.iterationNudgeThreshold || 1))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function findLastNonIgnoredMessage(messages: WithParts[]): LastNonIgnoredMessage | null {
|
|
46
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
47
|
+
const message = messages[i]
|
|
48
|
+
if (isIgnoredUserMessage(message)) {
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
return { message, index: i }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function countMessagesAfterIndex(messages: WithParts[], index: number): number {
|
|
58
|
+
let count = 0
|
|
59
|
+
|
|
60
|
+
for (let i = index + 1; i < messages.length; i++) {
|
|
61
|
+
const message = messages[i]
|
|
62
|
+
if (isIgnoredUserMessage(message)) {
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
count++
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return count
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getModelInfo(messages: WithParts[]): LastUserModelContext {
|
|
72
|
+
const lastUserMessage = getLastUserMessage(messages)
|
|
73
|
+
if (!lastUserMessage) {
|
|
74
|
+
return {
|
|
75
|
+
providerId: undefined,
|
|
76
|
+
modelId: undefined,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const userInfo = lastUserMessage.info as UserMessage
|
|
81
|
+
return {
|
|
82
|
+
providerId: userInfo.model.providerID,
|
|
83
|
+
modelId: userInfo.model.modelID,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveContextTokenLimit(
|
|
88
|
+
config: PluginConfig,
|
|
89
|
+
state: SessionState,
|
|
90
|
+
providerId: string | undefined,
|
|
91
|
+
modelId: string | undefined,
|
|
92
|
+
threshold: "max" | "min",
|
|
93
|
+
): number | undefined {
|
|
94
|
+
const parseLimitValue = (limit: number | `${number}%` | undefined): number | undefined => {
|
|
95
|
+
if (limit === undefined) {
|
|
96
|
+
return undefined
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof limit === "number") {
|
|
100
|
+
return limit
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!limit.endsWith("%") || state.modelContextLimit === undefined) {
|
|
104
|
+
return undefined
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const parsedPercent = parseFloat(limit.slice(0, -1))
|
|
108
|
+
if (isNaN(parsedPercent)) {
|
|
109
|
+
return undefined
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const roundedPercent = Math.round(parsedPercent)
|
|
113
|
+
const clampedPercent = Math.max(0, Math.min(100, roundedPercent))
|
|
114
|
+
return Math.round((clampedPercent / 100) * state.modelContextLimit)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const modelLimits =
|
|
118
|
+
threshold === "max" ? config.compress.modelMaxLimits : config.compress.modelMinLimits
|
|
119
|
+
if (modelLimits && providerId !== undefined && modelId !== undefined) {
|
|
120
|
+
const providerModelId = `${providerId}/${modelId}`
|
|
121
|
+
const modelLimit = modelLimits[providerModelId]
|
|
122
|
+
if (modelLimit !== undefined) {
|
|
123
|
+
return parseLimitValue(modelLimit)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const globalLimit =
|
|
128
|
+
threshold === "max" ? config.compress.maxContextLimit : config.compress.minContextLimit
|
|
129
|
+
return parseLimitValue(globalLimit)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function isContextOverLimits(
|
|
133
|
+
config: PluginConfig,
|
|
134
|
+
state: SessionState,
|
|
135
|
+
providerId: string | undefined,
|
|
136
|
+
modelId: string | undefined,
|
|
137
|
+
messages: WithParts[],
|
|
138
|
+
) {
|
|
139
|
+
const summaryTokenExtension = config.compress.summaryBuffer
|
|
140
|
+
? getActiveSummaryTokenUsage(state)
|
|
141
|
+
: 0
|
|
142
|
+
const resolvedMaxContextLimit = resolveContextTokenLimit(
|
|
143
|
+
config,
|
|
144
|
+
state,
|
|
145
|
+
providerId,
|
|
146
|
+
modelId,
|
|
147
|
+
"max",
|
|
148
|
+
)
|
|
149
|
+
const maxContextLimit =
|
|
150
|
+
resolvedMaxContextLimit === undefined
|
|
151
|
+
? undefined
|
|
152
|
+
: resolvedMaxContextLimit + summaryTokenExtension
|
|
153
|
+
const minContextLimit = resolveContextTokenLimit(config, state, providerId, modelId, "min")
|
|
154
|
+
const currentTokens = getCurrentTokenUsage(state, messages)
|
|
155
|
+
|
|
156
|
+
const overMaxLimit = maxContextLimit === undefined ? false : currentTokens > maxContextLimit
|
|
157
|
+
const overMinLimit = minContextLimit === undefined ? true : currentTokens >= minContextLimit
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
overMaxLimit,
|
|
161
|
+
overMinLimit,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function addAnchor(
|
|
166
|
+
anchorMessageIds: Set<string>,
|
|
167
|
+
anchorMessageId: string,
|
|
168
|
+
anchorMessageIndex: number,
|
|
169
|
+
messages: WithParts[],
|
|
170
|
+
interval: number,
|
|
171
|
+
): boolean {
|
|
172
|
+
if (anchorMessageIndex < 0) {
|
|
173
|
+
return false
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let latestAnchorMessageIndex = -1
|
|
177
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
178
|
+
if (anchorMessageIds.has(messages[i].info.id)) {
|
|
179
|
+
latestAnchorMessageIndex = i
|
|
180
|
+
break
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const shouldAdd =
|
|
185
|
+
latestAnchorMessageIndex < 0 || anchorMessageIndex - latestAnchorMessageIndex >= interval
|
|
186
|
+
if (!shouldAdd) {
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const previousSize = anchorMessageIds.size
|
|
191
|
+
anchorMessageIds.add(anchorMessageId)
|
|
192
|
+
return anchorMessageIds.size !== previousSize
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function buildMessagePriorityGuidance(
|
|
196
|
+
messages: WithParts[],
|
|
197
|
+
compressionPriorities: CompressionPriorityMap | undefined,
|
|
198
|
+
anchorIndex: number,
|
|
199
|
+
priority: MessagePriority,
|
|
200
|
+
): string {
|
|
201
|
+
if (!compressionPriorities || compressionPriorities.size === 0) {
|
|
202
|
+
return ""
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const refs = listPriorityRefsBeforeIndex(messages, compressionPriorities, anchorIndex, priority)
|
|
206
|
+
const priorityLabel = `${priority[0].toUpperCase()}${priority.slice(1)}`
|
|
207
|
+
|
|
208
|
+
return renderMessagePriorityGuidance(priorityLabel, refs)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function injectAnchoredNudge(message: WithParts, nudgeText: string): void {
|
|
212
|
+
if (!nudgeText.trim()) {
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (message.info.role === "user") {
|
|
217
|
+
if (appendToLastTextPart(message, nudgeText)) {
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
message.parts.push(createSyntheticTextPart(message, nudgeText))
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (message.info.role !== "assistant") {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!hasContent(message)) {
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const part of message.parts) {
|
|
234
|
+
if (part.type === "text") {
|
|
235
|
+
if (appendToTextPart(part, nudgeText)) {
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const syntheticPart = createSyntheticTextPart(message, nudgeText)
|
|
242
|
+
const firstToolIndex = message.parts.findIndex((p) => p.type === "tool")
|
|
243
|
+
if (firstToolIndex === -1) {
|
|
244
|
+
message.parts.push(syntheticPart)
|
|
245
|
+
} else {
|
|
246
|
+
message.parts.splice(firstToolIndex, 0, syntheticPart)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function collectAnchoredMessages(
|
|
251
|
+
anchorMessageIds: Set<string>,
|
|
252
|
+
messages: WithParts[],
|
|
253
|
+
): Array<{ message: WithParts; index: number }> {
|
|
254
|
+
const anchoredMessages: Array<{ message: WithParts; index: number }> = []
|
|
255
|
+
|
|
256
|
+
for (const anchorMessageId of anchorMessageIds) {
|
|
257
|
+
const index = messages.findIndex((message) => message.info.id === anchorMessageId)
|
|
258
|
+
if (index === -1) {
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
anchoredMessages.push({
|
|
263
|
+
message: messages[index],
|
|
264
|
+
index,
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return anchoredMessages
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function collectTurnNudgeAnchors(
|
|
272
|
+
state: SessionState,
|
|
273
|
+
config: PluginConfig,
|
|
274
|
+
messages: WithParts[],
|
|
275
|
+
): Set<string> {
|
|
276
|
+
const turnNudgeAnchors = new Set<string>()
|
|
277
|
+
const targetRole = config.compress.nudgeForce === "strong" ? "user" : "assistant"
|
|
278
|
+
|
|
279
|
+
for (const message of messages) {
|
|
280
|
+
if (!state.nudges.turnNudgeAnchors.has(message.info.id)) continue
|
|
281
|
+
|
|
282
|
+
if (message.info.role === targetRole) {
|
|
283
|
+
turnNudgeAnchors.add(message.info.id)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return turnNudgeAnchors
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function applyRangeModeAnchoredNudge(
|
|
291
|
+
anchorMessageIds: Set<string>,
|
|
292
|
+
messages: WithParts[],
|
|
293
|
+
baseNudgeText: string,
|
|
294
|
+
compressedBlockGuidance: string,
|
|
295
|
+
): void {
|
|
296
|
+
const nudgeText = appendGuidanceToDcpTag(baseNudgeText, compressedBlockGuidance)
|
|
297
|
+
if (!nudgeText.trim()) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
for (const { message } of collectAnchoredMessages(anchorMessageIds, messages)) {
|
|
302
|
+
injectAnchoredNudge(message, nudgeText)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function applyMessageModeAnchoredNudge(
|
|
307
|
+
anchorMessageIds: Set<string>,
|
|
308
|
+
messages: WithParts[],
|
|
309
|
+
baseNudgeText: string,
|
|
310
|
+
compressionPriorities?: CompressionPriorityMap,
|
|
311
|
+
): void {
|
|
312
|
+
for (const { message, index } of collectAnchoredMessages(anchorMessageIds, messages)) {
|
|
313
|
+
const priorityGuidance = buildMessagePriorityGuidance(
|
|
314
|
+
messages,
|
|
315
|
+
compressionPriorities,
|
|
316
|
+
index,
|
|
317
|
+
MESSAGE_MODE_NUDGE_PRIORITY,
|
|
318
|
+
)
|
|
319
|
+
const nudgeText = appendGuidanceToDcpTag(baseNudgeText, priorityGuidance)
|
|
320
|
+
injectAnchoredNudge(message, nudgeText)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function applyAnchoredNudges(
|
|
325
|
+
state: SessionState,
|
|
326
|
+
config: PluginConfig,
|
|
327
|
+
messages: WithParts[],
|
|
328
|
+
prompts: RuntimePrompts,
|
|
329
|
+
compressionPriorities?: CompressionPriorityMap,
|
|
330
|
+
): void {
|
|
331
|
+
const turnNudgeAnchors = collectTurnNudgeAnchors(state, config, messages)
|
|
332
|
+
|
|
333
|
+
if (config.compress.mode === "message") {
|
|
334
|
+
applyMessageModeAnchoredNudge(
|
|
335
|
+
state.nudges.contextLimitAnchors,
|
|
336
|
+
messages,
|
|
337
|
+
prompts.contextLimitNudge,
|
|
338
|
+
compressionPriorities,
|
|
339
|
+
)
|
|
340
|
+
applyMessageModeAnchoredNudge(
|
|
341
|
+
turnNudgeAnchors,
|
|
342
|
+
messages,
|
|
343
|
+
prompts.turnNudge,
|
|
344
|
+
compressionPriorities,
|
|
345
|
+
)
|
|
346
|
+
applyMessageModeAnchoredNudge(
|
|
347
|
+
state.nudges.iterationNudgeAnchors,
|
|
348
|
+
messages,
|
|
349
|
+
prompts.iterationNudge,
|
|
350
|
+
compressionPriorities,
|
|
351
|
+
)
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const compressedBlockGuidance = buildCompressedBlockGuidance(state)
|
|
356
|
+
applyRangeModeAnchoredNudge(
|
|
357
|
+
state.nudges.contextLimitAnchors,
|
|
358
|
+
messages,
|
|
359
|
+
prompts.contextLimitNudge,
|
|
360
|
+
compressedBlockGuidance,
|
|
361
|
+
)
|
|
362
|
+
applyRangeModeAnchoredNudge(
|
|
363
|
+
turnNudgeAnchors,
|
|
364
|
+
messages,
|
|
365
|
+
prompts.turnNudge,
|
|
366
|
+
compressedBlockGuidance,
|
|
367
|
+
)
|
|
368
|
+
applyRangeModeAnchoredNudge(
|
|
369
|
+
state.nudges.iterationNudgeAnchors,
|
|
370
|
+
messages,
|
|
371
|
+
prompts.iterationNudge,
|
|
372
|
+
compressedBlockGuidance,
|
|
373
|
+
)
|
|
374
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { PluginConfig } from "../config"
|
|
2
|
+
import { countAllMessageTokens } from "../token-utils"
|
|
3
|
+
import { isMessageCompacted } from "../state/utils"
|
|
4
|
+
import type { SessionState, WithParts } from "../state"
|
|
5
|
+
import { isIgnoredUserMessage, isProtectedUserMessage, messageHasCompress } from "./query"
|
|
6
|
+
|
|
7
|
+
const MEDIUM_PRIORITY_MIN_TOKENS = 500
|
|
8
|
+
const HIGH_PRIORITY_MIN_TOKENS = 5000
|
|
9
|
+
|
|
10
|
+
export type MessagePriority = "low" | "medium" | "high"
|
|
11
|
+
|
|
12
|
+
export interface CompressionPriorityEntry {
|
|
13
|
+
ref: string
|
|
14
|
+
tokenCount: number
|
|
15
|
+
priority: MessagePriority
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type CompressionPriorityMap = Map<string, CompressionPriorityEntry>
|
|
19
|
+
|
|
20
|
+
export function buildPriorityMap(
|
|
21
|
+
config: PluginConfig,
|
|
22
|
+
state: SessionState,
|
|
23
|
+
messages: WithParts[],
|
|
24
|
+
): CompressionPriorityMap {
|
|
25
|
+
if (config.compress.mode !== "message") {
|
|
26
|
+
return new Map()
|
|
27
|
+
}
|
|
28
|
+
const priorities: CompressionPriorityMap = new Map()
|
|
29
|
+
|
|
30
|
+
for (const message of messages) {
|
|
31
|
+
if (isIgnoredUserMessage(message)) {
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isProtectedUserMessage(config, message)) {
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isMessageCompacted(state, message)) {
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const rawMessageId = message.info.id
|
|
44
|
+
if (typeof rawMessageId !== "string" || rawMessageId.length === 0) {
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ref = state.messageIds.byRawId.get(rawMessageId)
|
|
49
|
+
if (!ref) {
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const tokenCount = countAllMessageTokens(message)
|
|
54
|
+
priorities.set(rawMessageId, {
|
|
55
|
+
ref,
|
|
56
|
+
tokenCount,
|
|
57
|
+
priority: messageHasCompress(message) ? "high" : classifyMessagePriority(tokenCount),
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return priorities
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function classifyMessagePriority(tokenCount: number): MessagePriority {
|
|
65
|
+
if (tokenCount >= HIGH_PRIORITY_MIN_TOKENS) {
|
|
66
|
+
return "high"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (tokenCount >= MEDIUM_PRIORITY_MIN_TOKENS) {
|
|
70
|
+
return "medium"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return "low"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function listPriorityRefsBeforeIndex(
|
|
77
|
+
messages: WithParts[],
|
|
78
|
+
priorities: CompressionPriorityMap,
|
|
79
|
+
anchorIndex: number,
|
|
80
|
+
priority: MessagePriority,
|
|
81
|
+
): string[] {
|
|
82
|
+
const refs: string[] = []
|
|
83
|
+
const seen = new Set<string>()
|
|
84
|
+
const upperBound = Math.max(0, Math.min(anchorIndex, messages.length))
|
|
85
|
+
|
|
86
|
+
for (let index = 0; index < upperBound; index++) {
|
|
87
|
+
const rawMessageId = messages[index]?.info.id
|
|
88
|
+
if (typeof rawMessageId !== "string") {
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const entry = priorities.get(rawMessageId)
|
|
93
|
+
if (!entry || entry.priority !== priority || seen.has(entry.ref)) {
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
seen.add(entry.ref)
|
|
98
|
+
refs.push(entry.ref)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return refs
|
|
102
|
+
}
|