@tarquinen/opencode-dcp 3.2.3-beta0 → 3.2.4-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/config.js.map +1 -1
- 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/config.ts +7 -7
- package/lib/message-ids.ts +172 -0
- package/package.json +3 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { PluginConfig } from "../config"
|
|
2
|
+
import type { Logger } from "../logger"
|
|
3
|
+
import type { PromptStore } from "../prompts/store"
|
|
4
|
+
import type { CompressionBlock, CompressionMode, SessionState, WithParts } from "../state"
|
|
5
|
+
|
|
6
|
+
export interface ToolContext {
|
|
7
|
+
client: any
|
|
8
|
+
state: SessionState
|
|
9
|
+
logger: Logger
|
|
10
|
+
config: PluginConfig
|
|
11
|
+
prompts: PromptStore
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CompressRangeEntry {
|
|
15
|
+
startId: string
|
|
16
|
+
endId: string
|
|
17
|
+
summary: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CompressRangeToolArgs {
|
|
21
|
+
topic: string
|
|
22
|
+
content: CompressRangeEntry[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CompressMessageEntry {
|
|
26
|
+
messageId: string
|
|
27
|
+
topic: string
|
|
28
|
+
summary: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CompressMessageToolArgs {
|
|
32
|
+
topic: string
|
|
33
|
+
content: CompressMessageEntry[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface BoundaryReference {
|
|
37
|
+
kind: "message" | "compressed-block"
|
|
38
|
+
rawIndex: number
|
|
39
|
+
messageId?: string
|
|
40
|
+
blockId?: number
|
|
41
|
+
anchorMessageId?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SearchContext {
|
|
45
|
+
rawMessages: WithParts[]
|
|
46
|
+
rawMessagesById: Map<string, WithParts>
|
|
47
|
+
rawIndexById: Map<string, number>
|
|
48
|
+
summaryByBlockId: Map<number, CompressionBlock>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SelectionResolution {
|
|
52
|
+
startReference: BoundaryReference
|
|
53
|
+
endReference: BoundaryReference
|
|
54
|
+
messageIds: string[]
|
|
55
|
+
messageTokenById: Map<string, number>
|
|
56
|
+
toolIds: string[]
|
|
57
|
+
requiredBlockIds: number[]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ResolvedMessageCompression {
|
|
61
|
+
entry: CompressMessageEntry
|
|
62
|
+
selection: SelectionResolution
|
|
63
|
+
anchorMessageId: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ResolvedRangeCompression {
|
|
67
|
+
index: number
|
|
68
|
+
entry: CompressRangeEntry
|
|
69
|
+
selection: SelectionResolution
|
|
70
|
+
anchorMessageId: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ResolvedMessageCompressionsResult {
|
|
74
|
+
plans: ResolvedMessageCompression[]
|
|
75
|
+
skippedIssues: string[]
|
|
76
|
+
skippedCount: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ParsedBlockPlaceholder {
|
|
80
|
+
raw: string
|
|
81
|
+
blockId: number
|
|
82
|
+
startIndex: number
|
|
83
|
+
endIndex: number
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface InjectedSummaryResult {
|
|
87
|
+
expandedSummary: string
|
|
88
|
+
consumedBlockIds: number[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface AppliedCompressionResult {
|
|
92
|
+
compressedTokens: number
|
|
93
|
+
messageIds: string[]
|
|
94
|
+
newlyCompressedMessageIds: string[]
|
|
95
|
+
newlyCompressedToolIds: string[]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface CompressionStateInput {
|
|
99
|
+
topic: string
|
|
100
|
+
batchTopic: string
|
|
101
|
+
startId: string
|
|
102
|
+
endId: string
|
|
103
|
+
mode: CompressionMode
|
|
104
|
+
runId: number
|
|
105
|
+
compressMessageId: string
|
|
106
|
+
compressCallId?: string
|
|
107
|
+
summaryTokens: number
|
|
108
|
+
}
|
package/lib/config.ts
CHANGED
|
@@ -642,7 +642,7 @@ function scheduleConfigWarning(
|
|
|
642
642
|
if (!notify) return
|
|
643
643
|
try {
|
|
644
644
|
notify(title, message)
|
|
645
|
-
} catch {
|
|
645
|
+
} catch {}
|
|
646
646
|
}, 7000)
|
|
647
647
|
}
|
|
648
648
|
|
|
@@ -766,8 +766,8 @@ function getConfigPaths(directory?: string): {
|
|
|
766
766
|
const global = existsSync(GLOBAL_CONFIG_PATH_JSONC)
|
|
767
767
|
? GLOBAL_CONFIG_PATH_JSONC
|
|
768
768
|
: existsSync(GLOBAL_CONFIG_PATH_JSON)
|
|
769
|
-
|
|
770
|
-
|
|
769
|
+
? GLOBAL_CONFIG_PATH_JSON
|
|
770
|
+
: null
|
|
771
771
|
|
|
772
772
|
let configDir: string | null = null
|
|
773
773
|
const opencodeConfigDir = process.env.OPENCODE_CONFIG_DIR
|
|
@@ -777,8 +777,8 @@ function getConfigPaths(directory?: string): {
|
|
|
777
777
|
configDir = existsSync(configJsonc)
|
|
778
778
|
? configJsonc
|
|
779
779
|
: existsSync(configJson)
|
|
780
|
-
|
|
781
|
-
|
|
780
|
+
? configJson
|
|
781
|
+
: null
|
|
782
782
|
}
|
|
783
783
|
|
|
784
784
|
let project: string | null = null
|
|
@@ -790,8 +790,8 @@ function getConfigPaths(directory?: string): {
|
|
|
790
790
|
project = existsSync(projectJsonc)
|
|
791
791
|
? projectJsonc
|
|
792
792
|
: existsSync(projectJson)
|
|
793
|
-
|
|
794
|
-
|
|
793
|
+
? projectJson
|
|
794
|
+
: null
|
|
795
795
|
}
|
|
796
796
|
}
|
|
797
797
|
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { SessionState, WithParts } from "./state"
|
|
2
|
+
import { isIgnoredUserMessage } from "./messages/query"
|
|
3
|
+
|
|
4
|
+
const MESSAGE_REF_REGEX = /^m(\d{4})$/
|
|
5
|
+
const BLOCK_REF_REGEX = /^b([1-9]\d*)$/
|
|
6
|
+
const MESSAGE_ID_TAG_NAME = "dcp-message-id"
|
|
7
|
+
|
|
8
|
+
const MESSAGE_REF_WIDTH = 4
|
|
9
|
+
const MESSAGE_REF_MIN_INDEX = 1
|
|
10
|
+
export const MESSAGE_REF_MAX_INDEX = 9999
|
|
11
|
+
|
|
12
|
+
export type ParsedBoundaryId =
|
|
13
|
+
| {
|
|
14
|
+
kind: "message"
|
|
15
|
+
ref: string
|
|
16
|
+
index: number
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
kind: "compressed-block"
|
|
20
|
+
ref: string
|
|
21
|
+
blockId: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatMessageRef(index: number): string {
|
|
25
|
+
if (
|
|
26
|
+
!Number.isInteger(index) ||
|
|
27
|
+
index < MESSAGE_REF_MIN_INDEX ||
|
|
28
|
+
index > MESSAGE_REF_MAX_INDEX
|
|
29
|
+
) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Message ID index out of bounds: ${index}. Supported range is 0-${MESSAGE_REF_MAX_INDEX}.`,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
return `m${index.toString().padStart(MESSAGE_REF_WIDTH, "0")}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function formatBlockRef(blockId: number): string {
|
|
38
|
+
if (!Number.isInteger(blockId) || blockId < 1) {
|
|
39
|
+
throw new Error(`Invalid block ID: ${blockId}`)
|
|
40
|
+
}
|
|
41
|
+
return `b${blockId}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parseMessageRef(ref: string): number | null {
|
|
45
|
+
const normalized = ref.trim().toLowerCase()
|
|
46
|
+
const match = normalized.match(MESSAGE_REF_REGEX)
|
|
47
|
+
if (!match) {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
const index = Number.parseInt(match[1], 10)
|
|
51
|
+
if (!Number.isInteger(index)) {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
if (index < MESSAGE_REF_MIN_INDEX || index > MESSAGE_REF_MAX_INDEX) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
return index
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function parseBlockRef(ref: string): number | null {
|
|
61
|
+
const normalized = ref.trim().toLowerCase()
|
|
62
|
+
const match = normalized.match(BLOCK_REF_REGEX)
|
|
63
|
+
if (!match) {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
const id = Number.parseInt(match[1], 10)
|
|
67
|
+
return Number.isInteger(id) ? id : null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function parseBoundaryId(id: string): ParsedBoundaryId | null {
|
|
71
|
+
const normalized = id.trim().toLowerCase()
|
|
72
|
+
const messageIndex = parseMessageRef(normalized)
|
|
73
|
+
if (messageIndex !== null) {
|
|
74
|
+
return {
|
|
75
|
+
kind: "message",
|
|
76
|
+
ref: formatMessageRef(messageIndex),
|
|
77
|
+
index: messageIndex,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const blockId = parseBlockRef(normalized)
|
|
82
|
+
if (blockId !== null) {
|
|
83
|
+
return {
|
|
84
|
+
kind: "compressed-block",
|
|
85
|
+
ref: formatBlockRef(blockId),
|
|
86
|
+
blockId,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function escapeXmlAttribute(value: string): string {
|
|
94
|
+
return value
|
|
95
|
+
.replace(/&/g, "&")
|
|
96
|
+
.replace(/"/g, """)
|
|
97
|
+
.replace(/</g, "<")
|
|
98
|
+
.replace(/>/g, ">")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function formatMessageIdTag(
|
|
102
|
+
ref: string,
|
|
103
|
+
attributes?: Record<string, string | undefined>,
|
|
104
|
+
): string {
|
|
105
|
+
const serializedAttributes = Object.entries(attributes || {})
|
|
106
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
107
|
+
.map(([name, value]) => {
|
|
108
|
+
if (name.trim().length === 0 || typeof value !== "string" || value.length === 0) {
|
|
109
|
+
return ""
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return ` ${name}="${escapeXmlAttribute(value)}"`
|
|
113
|
+
})
|
|
114
|
+
.join("")
|
|
115
|
+
|
|
116
|
+
return `\n<${MESSAGE_ID_TAG_NAME}${serializedAttributes}>${ref}</${MESSAGE_ID_TAG_NAME}>`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function assignMessageRefs(state: SessionState, messages: WithParts[]): number {
|
|
120
|
+
let assigned = 0
|
|
121
|
+
let skippedSubAgentPrompt = false
|
|
122
|
+
|
|
123
|
+
for (const message of messages) {
|
|
124
|
+
if (isIgnoredUserMessage(message)) {
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (state.isSubAgent && !skippedSubAgentPrompt && message.info.role === "user") {
|
|
129
|
+
skippedSubAgentPrompt = true
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const rawMessageId = message.info.id
|
|
134
|
+
if (typeof rawMessageId !== "string" || rawMessageId.length === 0) {
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const existingRef = state.messageIds.byRawId.get(rawMessageId)
|
|
139
|
+
if (existingRef) {
|
|
140
|
+
if (state.messageIds.byRef.get(existingRef) !== rawMessageId) {
|
|
141
|
+
state.messageIds.byRef.set(existingRef, rawMessageId)
|
|
142
|
+
}
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ref = allocateNextMessageRef(state)
|
|
147
|
+
state.messageIds.byRawId.set(rawMessageId, ref)
|
|
148
|
+
state.messageIds.byRef.set(ref, rawMessageId)
|
|
149
|
+
assigned++
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return assigned
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function allocateNextMessageRef(state: SessionState): string {
|
|
156
|
+
let candidate = Number.isInteger(state.messageIds.nextRef)
|
|
157
|
+
? Math.max(MESSAGE_REF_MIN_INDEX, state.messageIds.nextRef)
|
|
158
|
+
: MESSAGE_REF_MIN_INDEX
|
|
159
|
+
|
|
160
|
+
while (candidate <= MESSAGE_REF_MAX_INDEX) {
|
|
161
|
+
const ref = formatMessageRef(candidate)
|
|
162
|
+
if (!state.messageIds.byRef.has(ref)) {
|
|
163
|
+
state.messageIds.nextRef = candidate + 1
|
|
164
|
+
return ref
|
|
165
|
+
}
|
|
166
|
+
candidate++
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Message ID alias capacity exceeded. Cannot allocate more than ${formatMessageRef(MESSAGE_REF_MAX_INDEX)} aliases in this session.`,
|
|
171
|
+
)
|
|
172
|
+
}
|
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.4-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",
|
|
@@ -74,9 +74,11 @@
|
|
|
74
74
|
"files": [
|
|
75
75
|
"dist/",
|
|
76
76
|
"lib/analysis/",
|
|
77
|
+
"lib/compress/",
|
|
77
78
|
"lib/state/",
|
|
78
79
|
"lib/config.ts",
|
|
79
80
|
"lib/logger.ts",
|
|
81
|
+
"lib/message-ids.ts",
|
|
80
82
|
"lib/messages/query.ts",
|
|
81
83
|
"lib/token-utils.ts",
|
|
82
84
|
"tui/data/",
|