Skip to content

Commit f537689

Browse files
authored
Merge pull request #462 from Opencode-DCP/dev
v3.1.2 - Version bump
2 parents 8105874 + e7719e1 commit f537689

File tree

9 files changed

+94
-62
lines changed

9 files changed

+94
-62
lines changed

lib/compress/pipeline.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ export async function finalizeSession(
8686
await saveSessionState(ctx.state, ctx.logger)
8787

8888
const params = getCurrentParams(ctx.state, rawMessages, ctx.logger)
89-
const totalSessionTokens = getCurrentTokenUsage(ctx.state, rawMessages)
9089
const sessionMessageIds = rawMessages
9190
.filter((msg) => !isIgnoredUserMessage(msg))
9291
.map((msg) => msg.info.id)
@@ -99,7 +98,6 @@ export async function finalizeSession(
9998
toolCtx.sessionID,
10099
entries,
101100
batchTopic,
102-
totalSessionTokens,
103101
sessionMessageIds,
104102
params,
105103
)

lib/messages/inject/utils.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { appendToLastTextPart, createSyntheticTextPart, isIgnoredUserMessage } from "../utils"
1212
import { getLastUserMessage } from "../../shared-utils"
1313
import { getCurrentTokenUsage } from "../../strategies/utils"
14+
import { getActiveSummaryTokenUsage } from "../../state/utils"
1415

1516
const MESSAGE_MODE_NUDGE_PRIORITY: MessagePriority = "high"
1617

@@ -119,21 +120,6 @@ function resolveContextTokenLimit(
119120
return parseLimitValue(globalLimit)
120121
}
121122

122-
function getActiveSummaryTokenUsage(state: SessionState): number {
123-
let total = 0
124-
125-
for (const blockId of state.prune.messages.activeBlockIds) {
126-
const block = state.prune.messages.blocksById.get(blockId)
127-
if (!block || !block.active) {
128-
continue
129-
}
130-
131-
total += block.summaryTokens
132-
}
133-
134-
return total
135-
}
136-
137123
export function isContextOverLimits(
138124
config: PluginConfig,
139125
state: SessionState,

lib/messages/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import type { UserMessage } from "@opencode-ai/sdk/v2"
66

77
const SUMMARY_ID_HASH_LENGTH = 16
88
const DCP_BLOCK_ID_TAG_REGEX = /(<dcp-message-id(?=[\s>])[^>]*>)b\d+(<\/dcp-message-id>)/g
9-
const DCP_ANY_TAG_REGEX = /<dcp[^>]*>[\s\S]*?<\/dcp[^>]*>/gi
9+
const DCP_PAIRED_TAG_REGEX = /<dcp[^>]*>[\s\S]*?<\/dcp[^>]*>/gi
10+
const DCP_UNPAIRED_TAG_REGEX = /<\/?dcp[^>]*>/gi
1011

1112
const generateStableId = (prefix: string, seed: string): string => {
1213
const hash = createHash("sha256").update(seed).digest("hex").slice(0, SUMMARY_ID_HASH_LENGTH)
@@ -171,7 +172,7 @@ export const replaceBlockIdsWithBlocked = (text: string): string => {
171172
}
172173

173174
export const stripHallucinationsFromString = (text: string): string => {
174-
return text.replace(DCP_ANY_TAG_REGEX, "")
175+
return text.replace(DCP_PAIRED_TAG_REGEX, "").replace(DCP_UNPAIRED_TAG_REGEX, "")
175176
}
176177

177178
export const stripHallucinations = (messages: WithParts[]): void => {

lib/state/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,18 @@ export function collectTurnNudgeAnchors(messages: WithParts[]): Set<string> {
271271
return anchors
272272
}
273273

274+
export function getActiveSummaryTokenUsage(state: SessionState): number {
275+
let total = 0
276+
for (const blockId of state.prune.messages.activeBlockIds) {
277+
const block = state.prune.messages.blocksById.get(blockId)
278+
if (!block || !block.active) {
279+
continue
280+
}
281+
total += block.summaryTokens
282+
}
283+
return total
284+
}
285+
274286
export function resetOnCompaction(state: SessionState): void {
275287
state.toolParameters.clear()
276288
state.prune.tools = new Map<string, number>()

lib/ui/notification.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "./utils"
99
import { ToolParameterEntry } from "../state"
1010
import { PluginConfig } from "../config"
11+
import { getActiveSummaryTokenUsage } from "../state/utils"
1112

1213
export type PruneReason = "completion" | "noise" | "extraction"
1314
export const PRUNE_REASON_LABELS: Record<PruneReason, string> = {
@@ -168,7 +169,6 @@ export async function sendCompressNotification(
168169
sessionId: string,
169170
entries: CompressionNotificationEntry[],
170171
batchTopic: string | undefined,
171-
totalSessionTokens: number,
172172
sessionMessageIds: string[],
173173
params: any,
174174
): Promise<boolean> {
@@ -233,11 +233,17 @@ export async function sendCompressNotification(
233233
"(unknown topic)")
234234
: "(unknown topic)")
235235

236+
const totalActiveSummaryTkns = getActiveSummaryTokenUsage(state)
237+
const totalGross = state.stats.totalPruneTokens + state.stats.pruneTokenCounter
238+
const notificationHeader =
239+
totalActiveSummaryTkns > 0
240+
? `▣ DCP | ~${formatTokenCount(totalGross, true)} tokens removed (~${formatTokenCount(totalActiveSummaryTkns, true)} summary tokens added)`
241+
: `▣ DCP | ~${formatTokenCount(totalGross, true)} tokens removed`
242+
236243
if (config.pruneNotification === "minimal") {
237-
message = formatStatsHeader(state.stats.totalPruneTokens, state.stats.pruneTokenCounter)
238-
message += ` — ${compressionLabel}`
244+
message = `${notificationHeader}${compressionLabel}`
239245
} else {
240-
message = formatStatsHeader(state.stats.totalPruneTokens, state.stats.pruneTokenCounter)
246+
message = notificationHeader
241247

242248
const pruneTokenCounterStr = `~${formatTokenCount(compressedTokens)}`
243249

@@ -251,12 +257,10 @@ export async function sendCompressNotification(
251257
sessionMessageIds,
252258
activePrunedMessages,
253259
newlyCompressedMessageIds,
260+
70,
254261
)
255-
const reduction =
256-
totalSessionTokens > 0 ? Math.round((compressedTokens / totalSessionTokens) * 100) : 0
257-
258262
message += `\n\n${progressBar}`
259-
message += `\n▣ ${compressionLabel} (${pruneTokenCounterStr} removed, ${reduction}% reduction)`
263+
message += `\n▣ ${compressionLabel} (${pruneTokenCounterStr} removed, ~${formatTokenCount(summaryTokens, true)} summary tokens added)`
260264
message += `\n→ Topic: ${topic}`
261265
message += `\n→ Items: ${newlyCompressedMessageIds.length} messages`
262266
if (newlyCompressedToolIds.length > 0) {

lib/ui/utils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,12 @@ export function formatStatsHeader(totalTokensSaved: number, pruneTokenCounter: n
142142
return [`▣ DCP | ${totalTokensSavedStr} saved total`].join("\n")
143143
}
144144

145-
export function formatTokenCount(tokens: number): string {
145+
export function formatTokenCount(tokens: number, compact?: boolean): string {
146+
const suffix = compact ? "" : " tokens"
146147
if (tokens >= 1000) {
147-
return `${(tokens / 1000).toFixed(1)}K`.replace(".0K", "K") + " tokens"
148+
return `${(tokens / 1000).toFixed(1)}K`.replace(".0K", "K") + suffix
148149
}
149-
return tokens.toString() + " tokens"
150+
return tokens.toString() + suffix
150151
}
151152

152153
export function truncate(str: string, maxLen: number = 60): string {

package-lock.json

Lines changed: 23 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json.schemastore.org/package.json",
33
"name": "@tarquinen/opencode-dcp",
4-
"version": "3.1.1",
4+
"version": "3.1.2",
55
"type": "module",
66
"description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context",
77
"main": "./dist/index.js",
@@ -41,17 +41,17 @@
4141
},
4242
"dependencies": {
4343
"@anthropic-ai/tokenizer": "^0.0.4",
44-
"@opencode-ai/sdk": "^1.1.48",
44+
"@opencode-ai/sdk": "^1.3.2",
4545
"fuzzball": "^2.2.3",
4646
"jsonc-parser": "^3.3.1",
4747
"zod": "^4.3.6"
4848
},
4949
"devDependencies": {
50-
"@opencode-ai/plugin": "^1.1.49",
51-
"@types/node": "^25.1.0",
50+
"@opencode-ai/plugin": "^1.3.2",
51+
"@types/node": "^25.5.0",
5252
"prettier": "^3.8.1",
5353
"tsx": "^4.21.0",
54-
"typescript": "^5.9.3"
54+
"typescript": "^6.0.2"
5555
},
5656
"files": [
5757
"dist/",

tests/message-priority.test.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -629,14 +629,44 @@ test("hallucination stripping removes all dcp-prefixed XML tags including varian
629629
})
630630

631631
test("hallucination stripping removes colon and underscore dcp tag variants", async () => {
632+
assert.equal(stripHallucinationsFromString("beforeafter"), "beforeafter")
633+
assert.equal(stripHallucinationsFromString("startend"), "startend")
634+
})
635+
636+
test("hallucination stripping removes orphan opening tags", async () => {
637+
assert.equal(
638+
stripHallucinationsFromString("narration\n\n<dcp:function_calls>\n\n"),
639+
"narration\n\n\n\n",
640+
)
641+
assert.equal(stripHallucinationsFromString('text <dcp:invoke name="edit"> more'), "text more")
642+
})
643+
644+
test("hallucination stripping removes orphan closing tags", async () => {
645+
assert.equal(stripHallucinationsFromString("text</dcp:function_calls> more"), "text more")
646+
assert.equal(stripHallucinationsFromString("before</dcp-message-id>after"), "beforeafter")
647+
})
648+
649+
test("hallucination stripping handles nested dcp tags", async () => {
632650
assert.equal(
633-
stripHallucinationsFromString("before<dcp:message_id>m0074</dcp:message_id>after"),
634-
"beforeafter",
651+
stripHallucinationsFromString(
652+
'before<dcp:function_calls>\n<dcp:invoke name="edit">content</dcp:invoke>\n</dcp:function_calls>after',
653+
),
654+
"before\nafter",
635655
)
656+
})
657+
658+
test("hallucination stripping handles mixed paired and orphan tags", async () => {
636659
assert.equal(
637660
stripHallucinationsFromString(
638-
'start<dcp-function_calls><invoke name="Bash"></invoke></dcp-function_calls>end',
661+
'text\n<dcp-message-id priority="low">m0045</dcp-message-id>\n<dcp:function_calls>\n',
639662
),
640-
"startend",
663+
"text\n\n\n",
664+
)
665+
})
666+
667+
test("hallucination stripping does not affect non-dcp tags", async () => {
668+
assert.equal(
669+
stripHallucinationsFromString("<div>hello</div> <system-reminder>keep</system-reminder>"),
670+
"<div>hello</div> <system-reminder>keep</system-reminder>",
641671
)
642672
})

0 commit comments

Comments
 (0)