@tarquinen/opencode-dcp 3.2.1-beta0 → 3.2.3-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/README.md +4 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/commands/compression-targets.d.ts +1 -0
- package/dist/lib/commands/compression-targets.d.ts.map +1 -1
- package/dist/lib/commands/compression-targets.js +1 -0
- package/dist/lib/commands/compression-targets.js.map +1 -1
- package/dist/lib/commands/manual.js +1 -1
- package/dist/lib/commands/manual.js.map +1 -1
- package/dist/lib/commands/stats.d.ts.map +1 -1
- package/dist/lib/commands/stats.js +41 -6
- package/dist/lib/commands/stats.js.map +1 -1
- package/dist/lib/compress/message-utils.d.ts +2 -2
- package/dist/lib/compress/message-utils.d.ts.map +1 -1
- package/dist/lib/compress/message-utils.js +87 -31
- package/dist/lib/compress/message-utils.js.map +1 -1
- package/dist/lib/compress/message.d.ts.map +1 -1
- package/dist/lib/compress/message.js +10 -6
- package/dist/lib/compress/message.js.map +1 -1
- package/dist/lib/compress/pipeline.d.ts.map +1 -1
- package/dist/lib/compress/pipeline.js +2 -0
- package/dist/lib/compress/pipeline.js.map +1 -1
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/compress/range.js +6 -2
- package/dist/lib/compress/range.js.map +1 -1
- package/dist/lib/compress/state.d.ts +2 -1
- package/dist/lib/compress/state.d.ts.map +1 -1
- package/dist/lib/compress/state.js +16 -0
- package/dist/lib/compress/state.js.map +1 -1
- package/dist/lib/compress/timing.d.ts +18 -0
- package/dist/lib/compress/timing.d.ts.map +1 -0
- package/dist/lib/compress/timing.js +42 -0
- package/dist/lib/compress/timing.js.map +1 -0
- package/dist/lib/compress/types.d.ts +2 -0
- package/dist/lib/compress/types.d.ts.map +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/hooks.d.ts +3 -0
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/hooks.js +72 -2
- package/dist/lib/hooks.js.map +1 -1
- package/dist/lib/messages/inject/inject.js +2 -2
- package/dist/lib/messages/inject/utils.d.ts +0 -1
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.js +1 -27
- package/dist/lib/messages/inject/utils.js.map +1 -1
- package/dist/lib/messages/utils.d.ts +1 -1
- package/dist/lib/messages/utils.d.ts.map +1 -1
- package/dist/lib/messages/utils.js +5 -12
- package/dist/lib/messages/utils.js.map +1 -1
- package/dist/lib/prompts/compress-message.d.ts +1 -1
- package/dist/lib/prompts/compress-message.d.ts.map +1 -1
- package/dist/lib/prompts/compress-message.js +11 -12
- package/dist/lib/prompts/compress-message.js.map +1 -1
- package/dist/lib/prompts/compress-range.d.ts +1 -1
- package/dist/lib/prompts/compress-range.d.ts.map +1 -1
- package/dist/lib/prompts/compress-range.js +1 -1
- package/dist/lib/prompts/context-limit-nudge.d.ts +1 -1
- package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/context-limit-nudge.js +3 -9
- package/dist/lib/prompts/context-limit-nudge.js.map +1 -1
- package/dist/lib/prompts/extensions/nudge.d.ts +5 -0
- package/dist/lib/prompts/extensions/nudge.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/nudge.js +35 -0
- package/dist/lib/prompts/extensions/nudge.js.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/system.js +30 -0
- package/dist/lib/prompts/extensions/system.js.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/extensions/tool.js +34 -0
- package/dist/lib/prompts/extensions/tool.js.map +1 -0
- package/dist/lib/prompts/index.d.ts +1 -1
- package/dist/lib/prompts/index.d.ts.map +1 -1
- package/dist/lib/prompts/index.js +12 -13
- package/dist/lib/prompts/index.js.map +1 -1
- package/dist/lib/prompts/iteration-nudge.d.ts +1 -1
- package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/iteration-nudge.js +0 -2
- package/dist/lib/prompts/iteration-nudge.js.map +1 -1
- package/dist/lib/prompts/store.d.ts +2 -2
- package/dist/lib/prompts/store.d.ts.map +1 -1
- package/dist/lib/prompts/store.js +6 -6
- package/dist/lib/prompts/store.js.map +1 -1
- package/dist/lib/prompts/system.d.ts +1 -1
- package/dist/lib/prompts/system.d.ts.map +1 -1
- package/dist/lib/prompts/system.js +0 -13
- package/dist/lib/prompts/system.js.map +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/turn-nudge.js +1 -2
- package/dist/lib/prompts/turn-nudge.js.map +1 -1
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/persistence.js +13 -16
- package/dist/lib/state/persistence.js.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/state.js +9 -0
- package/dist/lib/state/state.js.map +1 -1
- package/dist/lib/state/types.d.ts +4 -0
- package/dist/lib/state/types.d.ts.map +1 -1
- package/dist/lib/state/utils.d.ts +7 -6
- package/dist/lib/state/utils.d.ts.map +1 -1
- package/dist/lib/state/utils.js +17 -0
- package/dist/lib/state/utils.js.map +1 -1
- package/lib/analysis/tokens.ts +225 -0
- package/lib/config.ts +1071 -0
- package/lib/logger.ts +235 -0
- package/lib/messages/query.ts +56 -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/token-utils.ts +162 -0
- package/package.json +22 -6
- 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
- package/dist/lib/prompts/internal-overlays.d.ts +0 -5
- package/dist/lib/prompts/internal-overlays.d.ts.map +0 -1
- package/dist/lib/prompts/internal-overlays.js +0 -49
- package/dist/lib/prompts/internal-overlays.js.map +0 -1
- package/dist/lib/prompts/message-priority-guidance.d.ts +0 -2
- package/dist/lib/prompts/message-priority-guidance.d.ts.map +0 -1
- package/dist/lib/prompts/message-priority-guidance.js +0 -9
- package/dist/lib/prompts/message-priority-guidance.js.map +0 -1
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CompressionBlock,
|
|
3
|
+
PruneMessagesState,
|
|
4
|
+
PrunedMessageEntry,
|
|
5
|
+
SessionState,
|
|
6
|
+
WithParts,
|
|
7
|
+
} from "./types"
|
|
8
|
+
import { isIgnoredUserMessage, messageHasCompress } from "../messages/query"
|
|
9
|
+
import { countTokens } from "../token-utils"
|
|
10
|
+
|
|
11
|
+
export const isMessageCompacted = (state: SessionState, msg: WithParts): boolean => {
|
|
12
|
+
if (msg.info.time.created < state.lastCompaction) {
|
|
13
|
+
return true
|
|
14
|
+
}
|
|
15
|
+
const pruneEntry = state.prune.messages.byMessageId.get(msg.info.id)
|
|
16
|
+
if (pruneEntry && pruneEntry.activeBlockIds.length > 0) {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface PersistedPruneMessagesState {
|
|
23
|
+
byMessageId: Record<string, PrunedMessageEntry>
|
|
24
|
+
blocksById: Record<string, CompressionBlock>
|
|
25
|
+
activeBlockIds: number[]
|
|
26
|
+
activeByAnchorMessageId: Record<string, number>
|
|
27
|
+
nextBlockId: number
|
|
28
|
+
nextRunId: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function serializePruneMessagesState(
|
|
32
|
+
messagesState: PruneMessagesState,
|
|
33
|
+
): PersistedPruneMessagesState {
|
|
34
|
+
return {
|
|
35
|
+
byMessageId: Object.fromEntries(messagesState.byMessageId),
|
|
36
|
+
blocksById: Object.fromEntries(
|
|
37
|
+
Array.from(messagesState.blocksById.entries()).map(([blockId, block]) => [
|
|
38
|
+
String(blockId),
|
|
39
|
+
block,
|
|
40
|
+
]),
|
|
41
|
+
),
|
|
42
|
+
activeBlockIds: Array.from(messagesState.activeBlockIds),
|
|
43
|
+
activeByAnchorMessageId: Object.fromEntries(messagesState.activeByAnchorMessageId),
|
|
44
|
+
nextBlockId: messagesState.nextBlockId,
|
|
45
|
+
nextRunId: messagesState.nextRunId,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function isSubAgentSession(client: any, sessionID: string): Promise<boolean> {
|
|
50
|
+
try {
|
|
51
|
+
const result = await client.session.get({ path: { id: sessionID } })
|
|
52
|
+
return !!result.data?.parentID
|
|
53
|
+
} catch (error: any) {
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function findLastCompactionTimestamp(messages: WithParts[]): number {
|
|
59
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
60
|
+
const msg = messages[i]
|
|
61
|
+
if (msg.info.role === "assistant" && msg.info.summary === true) {
|
|
62
|
+
return msg.info.time.created
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return 0
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function countTurns(state: SessionState, messages: WithParts[]): number {
|
|
69
|
+
let turnCount = 0
|
|
70
|
+
for (const msg of messages) {
|
|
71
|
+
if (isMessageCompacted(state, msg)) {
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : []
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
if (part.type === "step-start") {
|
|
77
|
+
turnCount++
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return turnCount
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function loadPruneMap(obj?: Record<string, number>): Map<string, number> {
|
|
85
|
+
if (!obj || typeof obj !== "object") {
|
|
86
|
+
return new Map()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const entries = Object.entries(obj).filter(
|
|
90
|
+
(entry): entry is [string, number] =>
|
|
91
|
+
typeof entry[0] === "string" && typeof entry[1] === "number",
|
|
92
|
+
)
|
|
93
|
+
return new Map(entries)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createPruneMessagesState(): PruneMessagesState {
|
|
97
|
+
return {
|
|
98
|
+
byMessageId: new Map<string, PrunedMessageEntry>(),
|
|
99
|
+
blocksById: new Map<number, CompressionBlock>(),
|
|
100
|
+
activeBlockIds: new Set<number>(),
|
|
101
|
+
activeByAnchorMessageId: new Map<string, number>(),
|
|
102
|
+
nextBlockId: 1,
|
|
103
|
+
nextRunId: 1,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function loadPruneMessagesState(
|
|
108
|
+
persisted?: PersistedPruneMessagesState,
|
|
109
|
+
): PruneMessagesState {
|
|
110
|
+
const state = createPruneMessagesState()
|
|
111
|
+
if (!persisted || typeof persisted !== "object") {
|
|
112
|
+
return state
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (typeof persisted.nextBlockId === "number" && Number.isInteger(persisted.nextBlockId)) {
|
|
116
|
+
state.nextBlockId = Math.max(1, persisted.nextBlockId)
|
|
117
|
+
}
|
|
118
|
+
if (typeof persisted.nextRunId === "number" && Number.isInteger(persisted.nextRunId)) {
|
|
119
|
+
state.nextRunId = Math.max(1, persisted.nextRunId)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (persisted.byMessageId && typeof persisted.byMessageId === "object") {
|
|
123
|
+
for (const [messageId, entry] of Object.entries(persisted.byMessageId)) {
|
|
124
|
+
if (!entry || typeof entry !== "object") {
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const tokenCount = typeof entry.tokenCount === "number" ? entry.tokenCount : 0
|
|
129
|
+
const allBlockIds = Array.isArray(entry.allBlockIds)
|
|
130
|
+
? [
|
|
131
|
+
...new Set(
|
|
132
|
+
entry.allBlockIds.filter(
|
|
133
|
+
(id): id is number => Number.isInteger(id) && id > 0,
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
]
|
|
137
|
+
: []
|
|
138
|
+
const activeBlockIds = Array.isArray(entry.activeBlockIds)
|
|
139
|
+
? [
|
|
140
|
+
...new Set(
|
|
141
|
+
entry.activeBlockIds.filter(
|
|
142
|
+
(id): id is number => Number.isInteger(id) && id > 0,
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
]
|
|
146
|
+
: []
|
|
147
|
+
|
|
148
|
+
state.byMessageId.set(messageId, {
|
|
149
|
+
tokenCount,
|
|
150
|
+
allBlockIds,
|
|
151
|
+
activeBlockIds,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (persisted.blocksById && typeof persisted.blocksById === "object") {
|
|
157
|
+
for (const [blockIdStr, block] of Object.entries(persisted.blocksById)) {
|
|
158
|
+
const blockId = Number.parseInt(blockIdStr, 10)
|
|
159
|
+
if (!Number.isInteger(blockId) || blockId < 1 || !block || typeof block !== "object") {
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const toNumberArray = (value: unknown): number[] =>
|
|
164
|
+
Array.isArray(value)
|
|
165
|
+
? [
|
|
166
|
+
...new Set(
|
|
167
|
+
value.filter(
|
|
168
|
+
(item): item is number => Number.isInteger(item) && item > 0,
|
|
169
|
+
),
|
|
170
|
+
),
|
|
171
|
+
]
|
|
172
|
+
: []
|
|
173
|
+
const toStringArray = (value: unknown): string[] =>
|
|
174
|
+
Array.isArray(value)
|
|
175
|
+
? [...new Set(value.filter((item): item is string => typeof item === "string"))]
|
|
176
|
+
: []
|
|
177
|
+
|
|
178
|
+
state.blocksById.set(blockId, {
|
|
179
|
+
blockId,
|
|
180
|
+
runId:
|
|
181
|
+
typeof block.runId === "number" &&
|
|
182
|
+
Number.isInteger(block.runId) &&
|
|
183
|
+
block.runId > 0
|
|
184
|
+
? block.runId
|
|
185
|
+
: blockId,
|
|
186
|
+
active: block.active === true,
|
|
187
|
+
deactivatedByUser: block.deactivatedByUser === true,
|
|
188
|
+
compressedTokens:
|
|
189
|
+
typeof block.compressedTokens === "number" &&
|
|
190
|
+
Number.isFinite(block.compressedTokens)
|
|
191
|
+
? Math.max(0, block.compressedTokens)
|
|
192
|
+
: 0,
|
|
193
|
+
summaryTokens:
|
|
194
|
+
typeof block.summaryTokens === "number" && Number.isFinite(block.summaryTokens)
|
|
195
|
+
? Math.max(0, block.summaryTokens)
|
|
196
|
+
: typeof block.summary === "string"
|
|
197
|
+
? countTokens(block.summary)
|
|
198
|
+
: 0,
|
|
199
|
+
durationMs:
|
|
200
|
+
typeof block.durationMs === "number" && Number.isFinite(block.durationMs)
|
|
201
|
+
? Math.max(0, block.durationMs)
|
|
202
|
+
: 0,
|
|
203
|
+
mode: block.mode === "range" || block.mode === "message" ? block.mode : undefined,
|
|
204
|
+
topic: typeof block.topic === "string" ? block.topic : "",
|
|
205
|
+
batchTopic:
|
|
206
|
+
typeof block.batchTopic === "string"
|
|
207
|
+
? block.batchTopic
|
|
208
|
+
: typeof block.topic === "string"
|
|
209
|
+
? block.topic
|
|
210
|
+
: "",
|
|
211
|
+
startId: typeof block.startId === "string" ? block.startId : "",
|
|
212
|
+
endId: typeof block.endId === "string" ? block.endId : "",
|
|
213
|
+
anchorMessageId:
|
|
214
|
+
typeof block.anchorMessageId === "string" ? block.anchorMessageId : "",
|
|
215
|
+
compressMessageId:
|
|
216
|
+
typeof block.compressMessageId === "string" ? block.compressMessageId : "",
|
|
217
|
+
compressCallId:
|
|
218
|
+
typeof block.compressCallId === "string" ? block.compressCallId : undefined,
|
|
219
|
+
includedBlockIds: toNumberArray(block.includedBlockIds),
|
|
220
|
+
consumedBlockIds: toNumberArray(block.consumedBlockIds),
|
|
221
|
+
parentBlockIds: toNumberArray(block.parentBlockIds),
|
|
222
|
+
directMessageIds: toStringArray(block.directMessageIds),
|
|
223
|
+
directToolIds: toStringArray(block.directToolIds),
|
|
224
|
+
effectiveMessageIds: toStringArray(block.effectiveMessageIds),
|
|
225
|
+
effectiveToolIds: toStringArray(block.effectiveToolIds),
|
|
226
|
+
createdAt: typeof block.createdAt === "number" ? block.createdAt : 0,
|
|
227
|
+
deactivatedAt:
|
|
228
|
+
typeof block.deactivatedAt === "number" ? block.deactivatedAt : undefined,
|
|
229
|
+
deactivatedByBlockId:
|
|
230
|
+
typeof block.deactivatedByBlockId === "number" &&
|
|
231
|
+
Number.isInteger(block.deactivatedByBlockId)
|
|
232
|
+
? block.deactivatedByBlockId
|
|
233
|
+
: undefined,
|
|
234
|
+
summary: typeof block.summary === "string" ? block.summary : "",
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (Array.isArray(persisted.activeBlockIds)) {
|
|
240
|
+
for (const blockId of persisted.activeBlockIds) {
|
|
241
|
+
if (!Number.isInteger(blockId) || blockId < 1) {
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
state.activeBlockIds.add(blockId)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (
|
|
249
|
+
persisted.activeByAnchorMessageId &&
|
|
250
|
+
typeof persisted.activeByAnchorMessageId === "object"
|
|
251
|
+
) {
|
|
252
|
+
for (const [anchorMessageId, blockId] of Object.entries(
|
|
253
|
+
persisted.activeByAnchorMessageId,
|
|
254
|
+
)) {
|
|
255
|
+
if (typeof blockId !== "number" || !Number.isInteger(blockId) || blockId < 1) {
|
|
256
|
+
continue
|
|
257
|
+
}
|
|
258
|
+
state.activeByAnchorMessageId.set(anchorMessageId, blockId)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
for (const [blockId, block] of state.blocksById) {
|
|
263
|
+
if (block.active) {
|
|
264
|
+
state.activeBlockIds.add(blockId)
|
|
265
|
+
if (block.anchorMessageId) {
|
|
266
|
+
state.activeByAnchorMessageId.set(block.anchorMessageId, blockId)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (blockId >= state.nextBlockId) {
|
|
270
|
+
state.nextBlockId = blockId + 1
|
|
271
|
+
}
|
|
272
|
+
if (block.runId >= state.nextRunId) {
|
|
273
|
+
state.nextRunId = block.runId + 1
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return state
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function collectTurnNudgeAnchors(messages: WithParts[]): Set<string> {
|
|
281
|
+
const anchors = new Set<string>()
|
|
282
|
+
let pendingUserMessageId: string | null = null
|
|
283
|
+
|
|
284
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
285
|
+
const message = messages[i]
|
|
286
|
+
|
|
287
|
+
if (messageHasCompress(message)) {
|
|
288
|
+
break
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (message.info.role === "user") {
|
|
292
|
+
if (!isIgnoredUserMessage(message)) {
|
|
293
|
+
pendingUserMessageId = message.info.id
|
|
294
|
+
}
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (message.info.role === "assistant" && pendingUserMessageId) {
|
|
299
|
+
anchors.add(message.info.id)
|
|
300
|
+
anchors.add(pendingUserMessageId)
|
|
301
|
+
pendingUserMessageId = null
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return anchors
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function getActiveSummaryTokenUsage(state: SessionState): number {
|
|
309
|
+
let total = 0
|
|
310
|
+
for (const blockId of state.prune.messages.activeBlockIds) {
|
|
311
|
+
const block = state.prune.messages.blocksById.get(blockId)
|
|
312
|
+
if (!block || !block.active) {
|
|
313
|
+
continue
|
|
314
|
+
}
|
|
315
|
+
total += block.summaryTokens
|
|
316
|
+
}
|
|
317
|
+
return total
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function resetOnCompaction(state: SessionState): void {
|
|
321
|
+
state.toolParameters.clear()
|
|
322
|
+
state.prune.tools = new Map<string, number>()
|
|
323
|
+
state.prune.messages = createPruneMessagesState()
|
|
324
|
+
state.messageIds = {
|
|
325
|
+
byRawId: new Map<string, string>(),
|
|
326
|
+
byRef: new Map<string, string>(),
|
|
327
|
+
nextRef: 1,
|
|
328
|
+
}
|
|
329
|
+
state.nudges = {
|
|
330
|
+
contextLimitAnchors: new Set<string>(),
|
|
331
|
+
turnNudgeAnchors: new Set<string>(),
|
|
332
|
+
iterationNudgeAnchors: new Set<string>(),
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { SessionState, WithParts } from "./state"
|
|
2
|
+
import { AssistantMessage, UserMessage } from "@opencode-ai/sdk/v2"
|
|
3
|
+
import { Logger } from "./logger"
|
|
4
|
+
import { countTokens as anthropicCountTokens } from "@anthropic-ai/tokenizer"
|
|
5
|
+
import { getLastUserMessage } from "./messages/query"
|
|
6
|
+
|
|
7
|
+
export function getCurrentTokenUsage(state: SessionState, messages: WithParts[]): number {
|
|
8
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
9
|
+
const msg = messages[i]
|
|
10
|
+
if (msg.info.role !== "assistant") {
|
|
11
|
+
continue
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const assistantInfo = msg.info as AssistantMessage
|
|
15
|
+
if ((assistantInfo.tokens?.output || 0) <= 0) {
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
state.lastCompaction > 0 &&
|
|
21
|
+
(msg.info.time.created < state.lastCompaction ||
|
|
22
|
+
(msg.info.summary === true && msg.info.time.created === state.lastCompaction))
|
|
23
|
+
) {
|
|
24
|
+
return 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const input = assistantInfo.tokens?.input || 0
|
|
28
|
+
const output = assistantInfo.tokens?.output || 0
|
|
29
|
+
const reasoning = assistantInfo.tokens?.reasoning || 0
|
|
30
|
+
const cacheRead = assistantInfo.tokens?.cache?.read || 0
|
|
31
|
+
const cacheWrite = assistantInfo.tokens?.cache?.write || 0
|
|
32
|
+
return input + output + reasoning + cacheRead + cacheWrite
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return 0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getCurrentParams(
|
|
39
|
+
state: SessionState,
|
|
40
|
+
messages: WithParts[],
|
|
41
|
+
logger: Logger,
|
|
42
|
+
): {
|
|
43
|
+
providerId: string | undefined
|
|
44
|
+
modelId: string | undefined
|
|
45
|
+
agent: string | undefined
|
|
46
|
+
variant: string | undefined
|
|
47
|
+
} {
|
|
48
|
+
const userMsg = getLastUserMessage(messages)
|
|
49
|
+
if (!userMsg) {
|
|
50
|
+
logger.debug("No user message found when determining current params")
|
|
51
|
+
return {
|
|
52
|
+
providerId: undefined,
|
|
53
|
+
modelId: undefined,
|
|
54
|
+
agent: undefined,
|
|
55
|
+
variant: state.variant,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const userInfo = userMsg.info as UserMessage
|
|
59
|
+
const agent: string = userInfo.agent
|
|
60
|
+
const providerId: string | undefined = userInfo.model.providerID
|
|
61
|
+
const modelId: string | undefined = userInfo.model.modelID
|
|
62
|
+
const variant: string | undefined = state.variant ?? userInfo.variant
|
|
63
|
+
|
|
64
|
+
return { providerId, modelId, agent, variant }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function countTokens(text: string): number {
|
|
68
|
+
if (!text) return 0
|
|
69
|
+
try {
|
|
70
|
+
return anthropicCountTokens(text)
|
|
71
|
+
} catch {
|
|
72
|
+
return Math.round(text.length / 4)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function estimateTokensBatch(texts: string[]): number {
|
|
77
|
+
if (texts.length === 0) return 0
|
|
78
|
+
return countTokens(texts.join(" "))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const COMPACTED_TOOL_OUTPUT_PLACEHOLDER = "[Old tool result content cleared]"
|
|
82
|
+
|
|
83
|
+
function stringifyToolContent(value: unknown): string {
|
|
84
|
+
return typeof value === "string" ? value : JSON.stringify(value)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function extractCompletedToolOutput(part: any): string | undefined {
|
|
88
|
+
if (
|
|
89
|
+
part?.type !== "tool" ||
|
|
90
|
+
part.state?.status !== "completed" ||
|
|
91
|
+
part.state?.output === undefined
|
|
92
|
+
) {
|
|
93
|
+
return undefined
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (part.state?.time?.compacted) {
|
|
97
|
+
return COMPACTED_TOOL_OUTPUT_PLACEHOLDER
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return stringifyToolContent(part.state.output)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function extractToolContent(part: any): string[] {
|
|
104
|
+
const contents: string[] = []
|
|
105
|
+
|
|
106
|
+
if (part?.type !== "tool") {
|
|
107
|
+
return contents
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (part.state?.input !== undefined) {
|
|
111
|
+
contents.push(stringifyToolContent(part.state.input))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const completedOutput = extractCompletedToolOutput(part)
|
|
115
|
+
if (completedOutput !== undefined) {
|
|
116
|
+
contents.push(completedOutput)
|
|
117
|
+
} else if (part.state?.status === "error" && part.state?.error) {
|
|
118
|
+
contents.push(stringifyToolContent(part.state.error))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return contents
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function countToolTokens(part: any): number {
|
|
125
|
+
const contents = extractToolContent(part)
|
|
126
|
+
return estimateTokensBatch(contents)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function getTotalToolTokens(state: SessionState, toolIds: string[]): number {
|
|
130
|
+
let total = 0
|
|
131
|
+
for (const id of toolIds) {
|
|
132
|
+
const entry = state.toolParameters.get(id)
|
|
133
|
+
total += entry?.tokenCount ?? 0
|
|
134
|
+
}
|
|
135
|
+
return total
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function countMessageTextTokens(msg: WithParts): number {
|
|
139
|
+
const texts: string[] = []
|
|
140
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : []
|
|
141
|
+
for (const part of parts) {
|
|
142
|
+
if (part.type === "text") {
|
|
143
|
+
texts.push(part.text)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (texts.length === 0) return 0
|
|
147
|
+
return estimateTokensBatch(texts)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function countAllMessageTokens(msg: WithParts): number {
|
|
151
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : []
|
|
152
|
+
const texts: string[] = []
|
|
153
|
+
for (const part of parts) {
|
|
154
|
+
if (part.type === "text") {
|
|
155
|
+
texts.push(part.text)
|
|
156
|
+
} else {
|
|
157
|
+
texts.push(...extractToolContent(part))
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (texts.length === 0) return 0
|
|
161
|
+
return estimateTokensBatch(texts)
|
|
162
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@tarquinen/opencode-dcp",
|
|
4
|
-
"version": "3.2.
|
|
4
|
+
"version": "3.2.3-beta0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"./tui": {
|
|
15
15
|
"types": "./dist/tui/index.d.ts",
|
|
16
|
-
"import": "./
|
|
16
|
+
"import": "./tui/index.tsx"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"oc-plugin": [
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
59
59
|
"@opencode-ai/sdk": "^1.3.2",
|
|
60
|
-
"@opentui/core": "0.
|
|
61
|
-
"@opentui/solid": "0.
|
|
60
|
+
"@opentui/core": "0.1.92",
|
|
61
|
+
"@opentui/solid": "0.1.92",
|
|
62
62
|
"fuzzball": "^2.2.3",
|
|
63
63
|
"jsonc-parser": "^3.3.1",
|
|
64
|
-
"solid-js": "1.9.
|
|
64
|
+
"solid-js": "1.9.11",
|
|
65
65
|
"zod": "^4.3.6"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
@@ -73,7 +73,23 @@
|
|
|
73
73
|
},
|
|
74
74
|
"files": [
|
|
75
75
|
"dist/",
|
|
76
|
+
"lib/analysis/",
|
|
77
|
+
"lib/state/",
|
|
78
|
+
"lib/config.ts",
|
|
79
|
+
"lib/logger.ts",
|
|
80
|
+
"lib/messages/query.ts",
|
|
81
|
+
"lib/token-utils.ts",
|
|
82
|
+
"tui/data/",
|
|
83
|
+
"tui/routes/",
|
|
84
|
+
"tui/shared/",
|
|
85
|
+
"tui/slots/",
|
|
86
|
+
"tui/index.tsx",
|
|
76
87
|
"README.md",
|
|
77
88
|
"LICENSE"
|
|
78
|
-
]
|
|
89
|
+
],
|
|
90
|
+
"directories": {
|
|
91
|
+
"doc": "docs",
|
|
92
|
+
"lib": "lib",
|
|
93
|
+
"test": "tests"
|
|
94
|
+
}
|
|
79
95
|
}
|