@tarquinen/opencode-dcp 3.2.4-beta0 → 3.2.5-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.
@@ -1,180 +0,0 @@
1
- import { tool } from "@opencode-ai/plugin"
2
- import type { ToolContext } from "./types"
3
- import { countTokens } from "../token-utils"
4
- import { RANGE_FORMAT_EXTENSION } from "../prompts/extensions/tool"
5
- import { finalizeSession, prepareSession, type NotificationEntry } from "./pipeline"
6
- import { appendProtectedTools, appendProtectedUserMessages } from "./protected-content"
7
- import {
8
- appendMissingBlockSummaries,
9
- injectBlockPlaceholders,
10
- parseBlockPlaceholders,
11
- resolveRanges,
12
- validateArgs,
13
- validateNonOverlapping,
14
- validateSummaryPlaceholders,
15
- } from "./range-utils"
16
- import {
17
- COMPRESSED_BLOCK_HEADER,
18
- allocateBlockId,
19
- allocateRunId,
20
- applyCompressionState,
21
- wrapCompressedSummary,
22
- } from "./state"
23
- import type { CompressRangeToolArgs } from "./types"
24
-
25
- function buildSchema() {
26
- return {
27
- topic: tool.schema
28
- .string()
29
- .describe("Short label (3-5 words) for display - e.g., 'Auth System Exploration'"),
30
- content: tool.schema
31
- .array(
32
- tool.schema.object({
33
- startId: tool.schema
34
- .string()
35
- .describe(
36
- "Message or block ID marking the beginning of range (e.g. m0001, b2)",
37
- ),
38
- endId: tool.schema
39
- .string()
40
- .describe("Message or block ID marking the end of range (e.g. m0012, b5)"),
41
- summary: tool.schema
42
- .string()
43
- .describe("Complete technical summary replacing all content in range"),
44
- }),
45
- )
46
- .describe(
47
- "One or more ranges to compress, each with start/end boundaries and a summary",
48
- ),
49
- }
50
- }
51
-
52
- export function createCompressRangeTool(ctx: ToolContext): ReturnType<typeof tool> {
53
- ctx.prompts.reload()
54
- const runtimePrompts = ctx.prompts.getRuntimePrompts()
55
-
56
- return tool({
57
- description: runtimePrompts.compressRange + RANGE_FORMAT_EXTENSION,
58
- args: buildSchema(),
59
- async execute(args, toolCtx) {
60
- const input = args as CompressRangeToolArgs
61
- validateArgs(input)
62
- const callId =
63
- typeof (toolCtx as unknown as { callID?: unknown }).callID === "string"
64
- ? (toolCtx as unknown as { callID: string }).callID
65
- : undefined
66
-
67
- const { rawMessages, searchContext } = await prepareSession(
68
- ctx,
69
- toolCtx,
70
- `Compress Range: ${input.topic}`,
71
- )
72
- const resolvedPlans = resolveRanges(input, searchContext, ctx.state)
73
- validateNonOverlapping(resolvedPlans)
74
-
75
- const notifications: NotificationEntry[] = []
76
- const preparedPlans: Array<{
77
- entry: (typeof resolvedPlans)[number]["entry"]
78
- selection: (typeof resolvedPlans)[number]["selection"]
79
- anchorMessageId: string
80
- finalSummary: string
81
- consumedBlockIds: number[]
82
- }> = []
83
- let totalCompressedMessages = 0
84
-
85
- for (const plan of resolvedPlans) {
86
- const parsedPlaceholders = parseBlockPlaceholders(plan.entry.summary)
87
- const missingBlockIds = validateSummaryPlaceholders(
88
- parsedPlaceholders,
89
- plan.selection.requiredBlockIds,
90
- plan.selection.startReference,
91
- plan.selection.endReference,
92
- searchContext.summaryByBlockId,
93
- )
94
-
95
- const injected = injectBlockPlaceholders(
96
- plan.entry.summary,
97
- parsedPlaceholders,
98
- searchContext.summaryByBlockId,
99
- plan.selection.startReference,
100
- plan.selection.endReference,
101
- )
102
-
103
- const summaryWithUsers = appendProtectedUserMessages(
104
- injected.expandedSummary,
105
- plan.selection,
106
- searchContext,
107
- ctx.state,
108
- ctx.config.compress.protectUserMessages,
109
- )
110
-
111
- const summaryWithTools = await appendProtectedTools(
112
- ctx.client,
113
- ctx.state,
114
- ctx.config.experimental.allowSubAgents,
115
- summaryWithUsers,
116
- plan.selection,
117
- searchContext,
118
- ctx.config.compress.protectedTools,
119
- ctx.config.protectedFilePatterns,
120
- )
121
-
122
- const completedSummary = appendMissingBlockSummaries(
123
- summaryWithTools,
124
- missingBlockIds,
125
- searchContext.summaryByBlockId,
126
- injected.consumedBlockIds,
127
- )
128
-
129
- preparedPlans.push({
130
- entry: plan.entry,
131
- selection: plan.selection,
132
- anchorMessageId: plan.anchorMessageId,
133
- finalSummary: completedSummary.expandedSummary,
134
- consumedBlockIds: completedSummary.consumedBlockIds,
135
- })
136
- }
137
-
138
- const runId = allocateRunId(ctx.state)
139
-
140
- for (const preparedPlan of preparedPlans) {
141
- const blockId = allocateBlockId(ctx.state)
142
- const storedSummary = wrapCompressedSummary(blockId, preparedPlan.finalSummary)
143
- const summaryTokens = countTokens(storedSummary)
144
-
145
- const applied = applyCompressionState(
146
- ctx.state,
147
- {
148
- topic: input.topic,
149
- batchTopic: input.topic,
150
- startId: preparedPlan.entry.startId,
151
- endId: preparedPlan.entry.endId,
152
- mode: "range",
153
- runId,
154
- compressMessageId: toolCtx.messageID,
155
- compressCallId: callId,
156
- summaryTokens,
157
- },
158
- preparedPlan.selection,
159
- preparedPlan.anchorMessageId,
160
- blockId,
161
- storedSummary,
162
- preparedPlan.consumedBlockIds,
163
- )
164
-
165
- totalCompressedMessages += applied.messageIds.length
166
-
167
- notifications.push({
168
- blockId,
169
- runId,
170
- summary: preparedPlan.finalSummary,
171
- summaryTokens,
172
- })
173
- }
174
-
175
- await finalizeSession(ctx, toolCtx, rawMessages, notifications, input.topic)
176
-
177
- return `Compressed ${totalCompressedMessages} messages into ${COMPRESSED_BLOCK_HEADER}.`
178
- },
179
- })
180
- }
@@ -1,267 +0,0 @@
1
- import type { SessionState, WithParts } from "../state"
2
- import { formatBlockRef, parseBoundaryId } from "../message-ids"
3
- import { isIgnoredUserMessage } from "../messages/query"
4
- import { countAllMessageTokens } from "../token-utils"
5
- import type { BoundaryReference, SearchContext, SelectionResolution } from "./types"
6
-
7
- export async function fetchSessionMessages(client: any, sessionId: string): Promise<WithParts[]> {
8
- const response = await client.session.messages({
9
- path: { id: sessionId },
10
- })
11
-
12
- const payload = (response?.data || response) as WithParts[]
13
- return Array.isArray(payload) ? payload : []
14
- }
15
-
16
- export function buildSearchContext(state: SessionState, rawMessages: WithParts[]): SearchContext {
17
- const rawMessagesById = new Map<string, WithParts>()
18
- const rawIndexById = new Map<string, number>()
19
- for (const msg of rawMessages) {
20
- rawMessagesById.set(msg.info.id, msg)
21
- }
22
- for (let index = 0; index < rawMessages.length; index++) {
23
- const message = rawMessages[index]
24
- if (!message) {
25
- continue
26
- }
27
- rawIndexById.set(message.info.id, index)
28
- }
29
-
30
- const summaryByBlockId = new Map()
31
- for (const [blockId, block] of state.prune.messages.blocksById) {
32
- if (!block.active) {
33
- continue
34
- }
35
- summaryByBlockId.set(blockId, block)
36
- }
37
-
38
- return {
39
- rawMessages,
40
- rawMessagesById,
41
- rawIndexById,
42
- summaryByBlockId,
43
- }
44
- }
45
-
46
- export function resolveBoundaryIds(
47
- context: SearchContext,
48
- state: SessionState,
49
- startId: string,
50
- endId: string,
51
- ): { startReference: BoundaryReference; endReference: BoundaryReference } {
52
- const lookup = buildBoundaryLookup(context, state)
53
- const issues: string[] = []
54
- const parsedStartId = parseBoundaryId(startId)
55
- const parsedEndId = parseBoundaryId(endId)
56
-
57
- if (parsedStartId === null) {
58
- issues.push("startId is invalid. Use an injected message ID (mNNNN) or block ID (bN).")
59
- }
60
-
61
- if (parsedEndId === null) {
62
- issues.push("endId is invalid. Use an injected message ID (mNNNN) or block ID (bN).")
63
- }
64
-
65
- if (issues.length > 0) {
66
- throw new Error(
67
- issues.length === 1 ? issues[0] : issues.map((issue) => `- ${issue}`).join("\n"),
68
- )
69
- }
70
-
71
- if (!parsedStartId || !parsedEndId) {
72
- throw new Error("Invalid boundary ID(s)")
73
- }
74
-
75
- const startReference = lookup.get(parsedStartId.ref)
76
- const endReference = lookup.get(parsedEndId.ref)
77
-
78
- if (!startReference) {
79
- issues.push(
80
- `startId ${parsedStartId.ref} is not available in the current conversation context. Choose an injected ID visible in context.`,
81
- )
82
- }
83
-
84
- if (!endReference) {
85
- issues.push(
86
- `endId ${parsedEndId.ref} is not available in the current conversation context. Choose an injected ID visible in context.`,
87
- )
88
- }
89
-
90
- if (issues.length > 0) {
91
- throw new Error(
92
- issues.length === 1 ? issues[0] : issues.map((issue) => `- ${issue}`).join("\n"),
93
- )
94
- }
95
-
96
- if (!startReference || !endReference) {
97
- throw new Error("Failed to resolve boundary IDs")
98
- }
99
-
100
- if (startReference.rawIndex > endReference.rawIndex) {
101
- throw new Error(
102
- `startId ${parsedStartId.ref} appears after endId ${parsedEndId.ref} in the conversation. Start must come before end.`,
103
- )
104
- }
105
-
106
- return { startReference, endReference }
107
- }
108
-
109
- export function resolveSelection(
110
- context: SearchContext,
111
- startReference: BoundaryReference,
112
- endReference: BoundaryReference,
113
- ): SelectionResolution {
114
- const startRawIndex = startReference.rawIndex
115
- const endRawIndex = endReference.rawIndex
116
- const messageIds: string[] = []
117
- const messageSeen = new Set<string>()
118
- const toolIds: string[] = []
119
- const toolSeen = new Set<string>()
120
- const requiredBlockIds: number[] = []
121
- const requiredBlockSeen = new Set<number>()
122
- const messageTokenById = new Map<string, number>()
123
-
124
- for (let index = startRawIndex; index <= endRawIndex; index++) {
125
- const rawMessage = context.rawMessages[index]
126
- if (!rawMessage) {
127
- continue
128
- }
129
- if (isIgnoredUserMessage(rawMessage)) {
130
- continue
131
- }
132
-
133
- const messageId = rawMessage.info.id
134
- if (!messageSeen.has(messageId)) {
135
- messageSeen.add(messageId)
136
- messageIds.push(messageId)
137
- }
138
-
139
- if (!messageTokenById.has(messageId)) {
140
- messageTokenById.set(messageId, countAllMessageTokens(rawMessage))
141
- }
142
-
143
- const parts = Array.isArray(rawMessage.parts) ? rawMessage.parts : []
144
- for (const part of parts) {
145
- if (part.type !== "tool" || !part.callID) {
146
- continue
147
- }
148
- if (toolSeen.has(part.callID)) {
149
- continue
150
- }
151
- toolSeen.add(part.callID)
152
- toolIds.push(part.callID)
153
- }
154
- }
155
-
156
- const selectedMessageIds = new Set(messageIds)
157
- const summariesInSelection: Array<{ blockId: number; rawIndex: number }> = []
158
- for (const summary of context.summaryByBlockId.values()) {
159
- if (!selectedMessageIds.has(summary.anchorMessageId)) {
160
- continue
161
- }
162
-
163
- const anchorIndex = context.rawIndexById.get(summary.anchorMessageId)
164
- if (anchorIndex === undefined) {
165
- continue
166
- }
167
-
168
- summariesInSelection.push({
169
- blockId: summary.blockId,
170
- rawIndex: anchorIndex,
171
- })
172
- }
173
-
174
- summariesInSelection.sort((a, b) => a.rawIndex - b.rawIndex || a.blockId - b.blockId)
175
- for (const summary of summariesInSelection) {
176
- if (requiredBlockSeen.has(summary.blockId)) {
177
- continue
178
- }
179
- requiredBlockSeen.add(summary.blockId)
180
- requiredBlockIds.push(summary.blockId)
181
- }
182
-
183
- if (messageIds.length === 0) {
184
- throw new Error(
185
- "Failed to map boundary matches back to raw messages. Choose boundaries that include original conversation messages.",
186
- )
187
- }
188
-
189
- return {
190
- startReference,
191
- endReference,
192
- messageIds,
193
- messageTokenById,
194
- toolIds,
195
- requiredBlockIds,
196
- }
197
- }
198
-
199
- export function resolveAnchorMessageId(startReference: BoundaryReference): string {
200
- if (startReference.kind === "compressed-block") {
201
- if (!startReference.anchorMessageId) {
202
- throw new Error("Failed to map boundary matches back to raw messages")
203
- }
204
- return startReference.anchorMessageId
205
- }
206
-
207
- if (!startReference.messageId) {
208
- throw new Error("Failed to map boundary matches back to raw messages")
209
- }
210
- return startReference.messageId
211
- }
212
-
213
- function buildBoundaryLookup(
214
- context: SearchContext,
215
- state: SessionState,
216
- ): Map<string, BoundaryReference> {
217
- const lookup = new Map<string, BoundaryReference>()
218
-
219
- for (const [messageRef, messageId] of state.messageIds.byRef) {
220
- const rawMessage = context.rawMessagesById.get(messageId)
221
- if (!rawMessage) {
222
- continue
223
- }
224
- if (isIgnoredUserMessage(rawMessage)) {
225
- continue
226
- }
227
-
228
- const rawIndex = context.rawIndexById.get(messageId)
229
- if (rawIndex === undefined) {
230
- continue
231
- }
232
- lookup.set(messageRef, {
233
- kind: "message",
234
- rawIndex,
235
- messageId,
236
- })
237
- }
238
-
239
- const summaries = Array.from(context.summaryByBlockId.values()).sort(
240
- (a, b) => a.blockId - b.blockId,
241
- )
242
- for (const summary of summaries) {
243
- const anchorMessage = context.rawMessagesById.get(summary.anchorMessageId)
244
- if (!anchorMessage) {
245
- continue
246
- }
247
- if (isIgnoredUserMessage(anchorMessage)) {
248
- continue
249
- }
250
-
251
- const rawIndex = context.rawIndexById.get(summary.anchorMessageId)
252
- if (rawIndex === undefined) {
253
- continue
254
- }
255
- const blockRef = formatBlockRef(summary.blockId)
256
- if (!lookup.has(blockRef)) {
257
- lookup.set(blockRef, {
258
- kind: "compressed-block",
259
- rawIndex,
260
- blockId: summary.blockId,
261
- anchorMessageId: summary.anchorMessageId,
262
- })
263
- }
264
- }
265
-
266
- return lookup
267
- }
@@ -1,268 +0,0 @@
1
- import type { CompressionBlock, PruneMessagesState, SessionState } from "../state"
2
- import { formatBlockRef, formatMessageIdTag } from "../message-ids"
3
- import type { AppliedCompressionResult, CompressionStateInput, SelectionResolution } from "./types"
4
-
5
- export const COMPRESSED_BLOCK_HEADER = "[Compressed conversation section]"
6
-
7
- export function allocateBlockId(state: SessionState): number {
8
- const next = state.prune.messages.nextBlockId
9
- if (!Number.isInteger(next) || next < 1) {
10
- state.prune.messages.nextBlockId = 2
11
- return 1
12
- }
13
-
14
- state.prune.messages.nextBlockId = next + 1
15
- return next
16
- }
17
-
18
- export function allocateRunId(state: SessionState): number {
19
- const next = state.prune.messages.nextRunId
20
- if (!Number.isInteger(next) || next < 1) {
21
- state.prune.messages.nextRunId = 2
22
- return 1
23
- }
24
-
25
- state.prune.messages.nextRunId = next + 1
26
- return next
27
- }
28
-
29
- export function attachCompressionDuration(
30
- messagesState: PruneMessagesState,
31
- messageId: string,
32
- callId: string,
33
- durationMs: number,
34
- ): number {
35
- if (typeof durationMs !== "number" || !Number.isFinite(durationMs)) {
36
- return 0
37
- }
38
-
39
- let updates = 0
40
- for (const block of messagesState.blocksById.values()) {
41
- if (block.compressMessageId !== messageId || block.compressCallId !== callId) {
42
- continue
43
- }
44
-
45
- block.durationMs = durationMs
46
- updates++
47
- }
48
-
49
- return updates
50
- }
51
-
52
- export function wrapCompressedSummary(blockId: number, summary: string): string {
53
- const header = COMPRESSED_BLOCK_HEADER
54
- const footer = formatMessageIdTag(formatBlockRef(blockId))
55
- const body = summary.trim()
56
- if (body.length === 0) {
57
- return `${header}\n${footer}`
58
- }
59
- return `${header}\n${body}\n\n${footer}`
60
- }
61
-
62
- export function applyCompressionState(
63
- state: SessionState,
64
- input: CompressionStateInput,
65
- selection: SelectionResolution,
66
- anchorMessageId: string,
67
- blockId: number,
68
- summary: string,
69
- consumedBlockIds: number[],
70
- ): AppliedCompressionResult {
71
- const messagesState = state.prune.messages
72
- const consumed = [...new Set(consumedBlockIds.filter((id) => Number.isInteger(id) && id > 0))]
73
- const included = [...consumed]
74
-
75
- const effectiveMessageIds = new Set<string>(selection.messageIds)
76
- const effectiveToolIds = new Set<string>(selection.toolIds)
77
-
78
- for (const consumedBlockId of consumed) {
79
- const consumedBlock = messagesState.blocksById.get(consumedBlockId)
80
- if (!consumedBlock) {
81
- continue
82
- }
83
- for (const messageId of consumedBlock.effectiveMessageIds) {
84
- effectiveMessageIds.add(messageId)
85
- }
86
- for (const toolId of consumedBlock.effectiveToolIds) {
87
- effectiveToolIds.add(toolId)
88
- }
89
- }
90
-
91
- const initiallyActiveMessages = new Set<string>()
92
- for (const messageId of effectiveMessageIds) {
93
- const entry = messagesState.byMessageId.get(messageId)
94
- if (entry && entry.activeBlockIds.length > 0) {
95
- initiallyActiveMessages.add(messageId)
96
- }
97
- }
98
-
99
- const initiallyActiveToolIds = new Set<string>()
100
- for (const activeBlockId of messagesState.activeBlockIds) {
101
- const activeBlock = messagesState.blocksById.get(activeBlockId)
102
- if (!activeBlock || !activeBlock.active) {
103
- continue
104
- }
105
-
106
- for (const toolId of activeBlock.effectiveToolIds) {
107
- initiallyActiveToolIds.add(toolId)
108
- }
109
- }
110
-
111
- const createdAt = Date.now()
112
- const block: CompressionBlock = {
113
- blockId,
114
- runId: input.runId,
115
- active: true,
116
- deactivatedByUser: false,
117
- compressedTokens: 0,
118
- summaryTokens: input.summaryTokens,
119
- durationMs: 0,
120
- mode: input.mode,
121
- topic: input.topic,
122
- batchTopic: input.batchTopic,
123
- startId: input.startId,
124
- endId: input.endId,
125
- anchorMessageId,
126
- compressMessageId: input.compressMessageId,
127
- compressCallId: input.compressCallId,
128
- includedBlockIds: included,
129
- consumedBlockIds: consumed,
130
- parentBlockIds: [],
131
- directMessageIds: [],
132
- directToolIds: [],
133
- effectiveMessageIds: [...effectiveMessageIds],
134
- effectiveToolIds: [...effectiveToolIds],
135
- createdAt,
136
- summary,
137
- }
138
-
139
- messagesState.blocksById.set(blockId, block)
140
- messagesState.activeBlockIds.add(blockId)
141
- messagesState.activeByAnchorMessageId.set(anchorMessageId, blockId)
142
-
143
- const deactivatedAt = Date.now()
144
- for (const consumedBlockId of consumed) {
145
- const consumedBlock = messagesState.blocksById.get(consumedBlockId)
146
- if (!consumedBlock || !consumedBlock.active) {
147
- continue
148
- }
149
-
150
- consumedBlock.active = false
151
- consumedBlock.deactivatedAt = deactivatedAt
152
- consumedBlock.deactivatedByBlockId = blockId
153
- if (!consumedBlock.parentBlockIds.includes(blockId)) {
154
- consumedBlock.parentBlockIds.push(blockId)
155
- }
156
-
157
- messagesState.activeBlockIds.delete(consumedBlockId)
158
- const mappedBlockId = messagesState.activeByAnchorMessageId.get(
159
- consumedBlock.anchorMessageId,
160
- )
161
- if (mappedBlockId === consumedBlockId) {
162
- messagesState.activeByAnchorMessageId.delete(consumedBlock.anchorMessageId)
163
- }
164
- }
165
-
166
- const removeActiveBlockId = (
167
- entry: { activeBlockIds: number[] },
168
- blockIdToRemove: number,
169
- ): void => {
170
- if (entry.activeBlockIds.length === 0) {
171
- return
172
- }
173
- entry.activeBlockIds = entry.activeBlockIds.filter((id) => id !== blockIdToRemove)
174
- }
175
-
176
- for (const consumedBlockId of consumed) {
177
- const consumedBlock = messagesState.blocksById.get(consumedBlockId)
178
- if (!consumedBlock) {
179
- continue
180
- }
181
- for (const messageId of consumedBlock.effectiveMessageIds) {
182
- const entry = messagesState.byMessageId.get(messageId)
183
- if (!entry) {
184
- continue
185
- }
186
- removeActiveBlockId(entry, consumedBlockId)
187
- }
188
- }
189
-
190
- for (const messageId of selection.messageIds) {
191
- const tokenCount = selection.messageTokenById.get(messageId) || 0
192
- const existing = messagesState.byMessageId.get(messageId)
193
-
194
- if (!existing) {
195
- messagesState.byMessageId.set(messageId, {
196
- tokenCount,
197
- allBlockIds: [blockId],
198
- activeBlockIds: [blockId],
199
- })
200
- continue
201
- }
202
-
203
- existing.tokenCount = Math.max(existing.tokenCount, tokenCount)
204
- if (!existing.allBlockIds.includes(blockId)) {
205
- existing.allBlockIds.push(blockId)
206
- }
207
- if (!existing.activeBlockIds.includes(blockId)) {
208
- existing.activeBlockIds.push(blockId)
209
- }
210
- }
211
-
212
- for (const messageId of block.effectiveMessageIds) {
213
- if (selection.messageTokenById.has(messageId)) {
214
- continue
215
- }
216
-
217
- const existing = messagesState.byMessageId.get(messageId)
218
- if (!existing) {
219
- continue
220
- }
221
- if (!existing.allBlockIds.includes(blockId)) {
222
- existing.allBlockIds.push(blockId)
223
- }
224
- if (!existing.activeBlockIds.includes(blockId)) {
225
- existing.activeBlockIds.push(blockId)
226
- }
227
- }
228
-
229
- let compressedTokens = 0
230
- const newlyCompressedMessageIds: string[] = []
231
- for (const messageId of effectiveMessageIds) {
232
- const entry = messagesState.byMessageId.get(messageId)
233
- if (!entry) {
234
- continue
235
- }
236
-
237
- const isNowActive = entry.activeBlockIds.length > 0
238
- const wasActive = initiallyActiveMessages.has(messageId)
239
-
240
- if (isNowActive && !wasActive) {
241
- compressedTokens += entry.tokenCount
242
- newlyCompressedMessageIds.push(messageId)
243
- }
244
- }
245
-
246
- const newlyCompressedToolIds: string[] = []
247
- for (const toolId of effectiveToolIds) {
248
- if (!initiallyActiveToolIds.has(toolId)) {
249
- newlyCompressedToolIds.push(toolId)
250
- }
251
- }
252
-
253
- block.directMessageIds = [...newlyCompressedMessageIds]
254
- block.directToolIds = [...newlyCompressedToolIds]
255
-
256
- block.compressedTokens = compressedTokens
257
-
258
- state.stats.pruneTokenCounter += compressedTokens
259
- state.stats.totalPruneTokens += state.stats.pruneTokenCounter
260
- state.stats.pruneTokenCounter = 0
261
-
262
- return {
263
- compressedTokens,
264
- messageIds: selection.messageIds,
265
- newlyCompressedMessageIds,
266
- newlyCompressedToolIds,
267
- }
268
- }