@tarquinen/opencode-dcp 3.2.5-beta0 → 3.2.6-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.
Files changed (72) hide show
  1. package/index.ts +141 -0
  2. package/lib/analysis/tokens.ts +225 -0
  3. package/lib/auth.ts +37 -0
  4. package/lib/commands/compression-targets.ts +137 -0
  5. package/lib/commands/context.ts +132 -0
  6. package/lib/commands/decompress.ts +275 -0
  7. package/lib/commands/help.ts +76 -0
  8. package/lib/commands/index.ts +11 -0
  9. package/lib/commands/manual.ts +125 -0
  10. package/lib/commands/recompress.ts +224 -0
  11. package/lib/commands/stats.ts +148 -0
  12. package/lib/commands/sweep.ts +268 -0
  13. package/lib/compress/index.ts +3 -0
  14. package/lib/compress/message-utils.ts +250 -0
  15. package/lib/compress/message.ts +137 -0
  16. package/lib/compress/pipeline.ts +106 -0
  17. package/lib/compress/protected-content.ts +154 -0
  18. package/lib/compress/range-utils.ts +308 -0
  19. package/lib/compress/range.ts +180 -0
  20. package/lib/compress/search.ts +267 -0
  21. package/lib/compress/state.ts +268 -0
  22. package/lib/compress/timing.ts +77 -0
  23. package/lib/compress/types.ts +108 -0
  24. package/lib/compress-permission.ts +25 -0
  25. package/lib/config.ts +1071 -0
  26. package/lib/hooks.ts +378 -0
  27. package/lib/host-permissions.ts +101 -0
  28. package/lib/logger.ts +235 -0
  29. package/lib/message-ids.ts +172 -0
  30. package/lib/messages/index.ts +8 -0
  31. package/lib/messages/inject/inject.ts +215 -0
  32. package/lib/messages/inject/subagent-results.ts +82 -0
  33. package/lib/messages/inject/utils.ts +374 -0
  34. package/lib/messages/priority.ts +102 -0
  35. package/lib/messages/prune.ts +238 -0
  36. package/lib/messages/query.ts +56 -0
  37. package/lib/messages/reasoning-strip.ts +40 -0
  38. package/lib/messages/sync.ts +124 -0
  39. package/lib/messages/utils.ts +187 -0
  40. package/lib/prompts/compress-message.ts +42 -0
  41. package/lib/prompts/compress-range.ts +60 -0
  42. package/lib/prompts/context-limit-nudge.ts +18 -0
  43. package/lib/prompts/extensions/nudge.ts +43 -0
  44. package/lib/prompts/extensions/system.ts +32 -0
  45. package/lib/prompts/extensions/tool.ts +35 -0
  46. package/lib/prompts/index.ts +29 -0
  47. package/lib/prompts/iteration-nudge.ts +6 -0
  48. package/lib/prompts/store.ts +467 -0
  49. package/lib/prompts/system.ts +33 -0
  50. package/lib/prompts/turn-nudge.ts +10 -0
  51. package/lib/protected-patterns.ts +128 -0
  52. package/lib/state/index.ts +4 -0
  53. package/lib/state/persistence.ts +256 -0
  54. package/lib/state/state.ts +190 -0
  55. package/lib/state/tool-cache.ts +98 -0
  56. package/lib/state/types.ts +112 -0
  57. package/lib/state/utils.ts +334 -0
  58. package/lib/strategies/deduplication.ts +127 -0
  59. package/lib/strategies/index.ts +2 -0
  60. package/lib/strategies/purge-errors.ts +88 -0
  61. package/lib/subagents/subagent-results.ts +74 -0
  62. package/lib/token-utils.ts +162 -0
  63. package/lib/ui/notification.ts +346 -0
  64. package/lib/ui/utils.ts +287 -0
  65. package/package.json +9 -2
  66. package/tui/data/context.ts +177 -0
  67. package/tui/index.tsx +34 -0
  68. package/tui/routes/summary.tsx +175 -0
  69. package/tui/shared/names.ts +9 -0
  70. package/tui/shared/theme.ts +58 -0
  71. package/tui/shared/types.ts +38 -0
  72. package/tui/slots/sidebar-content.tsx +502 -0
@@ -0,0 +1,256 @@
1
+ /**
2
+ * State persistence module for DCP plugin.
3
+ * Persists pruned tool IDs across sessions so they survive OpenCode restarts.
4
+ * Storage location: ~/.local/share/opencode/storage/plugin/dcp/{sessionId}.json
5
+ */
6
+
7
+ import * as fs from "fs/promises"
8
+ import { existsSync } from "fs"
9
+ import { homedir } from "os"
10
+ import { join } from "path"
11
+ import type { CompressionBlock, PrunedMessageEntry, SessionState, SessionStats } from "./types"
12
+ import type { Logger } from "../logger"
13
+ import { serializePruneMessagesState } from "./utils"
14
+
15
+ /** Prune state as stored on disk */
16
+ export interface PersistedPruneMessagesState {
17
+ byMessageId: Record<string, PrunedMessageEntry>
18
+ blocksById: Record<string, CompressionBlock>
19
+ activeBlockIds: number[]
20
+ activeByAnchorMessageId: Record<string, number>
21
+ nextBlockId: number
22
+ nextRunId: number
23
+ }
24
+
25
+ export interface PersistedPrune {
26
+ tools?: Record<string, number>
27
+ messages?: PersistedPruneMessagesState
28
+ }
29
+
30
+ export interface PersistedNudges {
31
+ contextLimitAnchors: string[]
32
+ turnNudgeAnchors?: string[]
33
+ iterationNudgeAnchors?: string[]
34
+ }
35
+
36
+ export interface PersistedSessionState {
37
+ sessionName?: string
38
+ prune: PersistedPrune
39
+ nudges: PersistedNudges
40
+ stats: SessionStats
41
+ lastUpdated: string
42
+ }
43
+
44
+ const STORAGE_DIR = join(
45
+ process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"),
46
+ "opencode",
47
+ "storage",
48
+ "plugin",
49
+ "dcp",
50
+ )
51
+
52
+ async function ensureStorageDir(): Promise<void> {
53
+ if (!existsSync(STORAGE_DIR)) {
54
+ await fs.mkdir(STORAGE_DIR, { recursive: true })
55
+ }
56
+ }
57
+
58
+ function getSessionFilePath(sessionId: string): string {
59
+ return join(STORAGE_DIR, `${sessionId}.json`)
60
+ }
61
+
62
+ async function writePersistedSessionState(
63
+ sessionId: string,
64
+ state: PersistedSessionState,
65
+ logger: Logger,
66
+ ): Promise<void> {
67
+ await ensureStorageDir()
68
+
69
+ const filePath = getSessionFilePath(sessionId)
70
+ const content = JSON.stringify(state, null, 2)
71
+ await fs.writeFile(filePath, content, "utf-8")
72
+
73
+ logger.info("Saved session state to disk", {
74
+ sessionId,
75
+ totalTokensSaved: state.stats.totalPruneTokens,
76
+ })
77
+ }
78
+
79
+ export async function saveSessionState(
80
+ sessionState: SessionState,
81
+ logger: Logger,
82
+ sessionName?: string,
83
+ ): Promise<void> {
84
+ try {
85
+ if (!sessionState.sessionId) {
86
+ return
87
+ }
88
+
89
+ const state: PersistedSessionState = {
90
+ sessionName: sessionName,
91
+ prune: {
92
+ tools: Object.fromEntries(sessionState.prune.tools),
93
+ messages: serializePruneMessagesState(sessionState.prune.messages),
94
+ },
95
+ nudges: {
96
+ contextLimitAnchors: Array.from(sessionState.nudges.contextLimitAnchors),
97
+ turnNudgeAnchors: Array.from(sessionState.nudges.turnNudgeAnchors),
98
+ iterationNudgeAnchors: Array.from(sessionState.nudges.iterationNudgeAnchors),
99
+ },
100
+ stats: sessionState.stats,
101
+ lastUpdated: new Date().toISOString(),
102
+ }
103
+
104
+ await writePersistedSessionState(sessionState.sessionId, state, logger)
105
+ } catch (error: any) {
106
+ logger.error("Failed to save session state", {
107
+ sessionId: sessionState.sessionId,
108
+ error: error?.message,
109
+ })
110
+ }
111
+ }
112
+
113
+ export async function loadSessionState(
114
+ sessionId: string,
115
+ logger: Logger,
116
+ ): Promise<PersistedSessionState | null> {
117
+ try {
118
+ const filePath = getSessionFilePath(sessionId)
119
+
120
+ if (!existsSync(filePath)) {
121
+ return null
122
+ }
123
+
124
+ const content = await fs.readFile(filePath, "utf-8")
125
+ const state = JSON.parse(content) as PersistedSessionState
126
+
127
+ const hasPruneTools = state?.prune?.tools && typeof state.prune.tools === "object"
128
+ const hasPruneMessages = state?.prune?.messages && typeof state.prune.messages === "object"
129
+ const hasNudgeFormat = state?.nudges && typeof state.nudges === "object"
130
+ if (
131
+ !state ||
132
+ !state.prune ||
133
+ !hasPruneTools ||
134
+ !hasPruneMessages ||
135
+ !state.stats ||
136
+ !hasNudgeFormat
137
+ ) {
138
+ logger.warn("Invalid session state file, ignoring", {
139
+ sessionId: sessionId,
140
+ })
141
+ return null
142
+ }
143
+
144
+ const rawContextLimitAnchors = Array.isArray(state.nudges.contextLimitAnchors)
145
+ ? state.nudges.contextLimitAnchors
146
+ : []
147
+ const validAnchors = rawContextLimitAnchors.filter(
148
+ (entry): entry is string => typeof entry === "string",
149
+ )
150
+ const dedupedAnchors = [...new Set(validAnchors)]
151
+ if (validAnchors.length !== rawContextLimitAnchors.length) {
152
+ logger.warn("Filtered out malformed contextLimitAnchors entries", {
153
+ sessionId: sessionId,
154
+ original: rawContextLimitAnchors.length,
155
+ valid: validAnchors.length,
156
+ })
157
+ }
158
+ state.nudges.contextLimitAnchors = dedupedAnchors
159
+
160
+ const rawTurnNudgeAnchors = Array.isArray(state.nudges.turnNudgeAnchors)
161
+ ? state.nudges.turnNudgeAnchors
162
+ : []
163
+ const validSoftAnchors = rawTurnNudgeAnchors.filter(
164
+ (entry): entry is string => typeof entry === "string",
165
+ )
166
+ const dedupedSoftAnchors = [...new Set(validSoftAnchors)]
167
+ if (validSoftAnchors.length !== rawTurnNudgeAnchors.length) {
168
+ logger.warn("Filtered out malformed turnNudgeAnchors entries", {
169
+ sessionId: sessionId,
170
+ original: rawTurnNudgeAnchors.length,
171
+ valid: validSoftAnchors.length,
172
+ })
173
+ }
174
+ state.nudges.turnNudgeAnchors = dedupedSoftAnchors
175
+
176
+ const rawIterationNudgeAnchors = Array.isArray(state.nudges.iterationNudgeAnchors)
177
+ ? state.nudges.iterationNudgeAnchors
178
+ : []
179
+ const validIterationAnchors = rawIterationNudgeAnchors.filter(
180
+ (entry): entry is string => typeof entry === "string",
181
+ )
182
+ const dedupedIterationAnchors = [...new Set(validIterationAnchors)]
183
+ if (validIterationAnchors.length !== rawIterationNudgeAnchors.length) {
184
+ logger.warn("Filtered out malformed iterationNudgeAnchors entries", {
185
+ sessionId: sessionId,
186
+ original: rawIterationNudgeAnchors.length,
187
+ valid: validIterationAnchors.length,
188
+ })
189
+ }
190
+ state.nudges.iterationNudgeAnchors = dedupedIterationAnchors
191
+
192
+ logger.info("Loaded session state from disk", {
193
+ sessionId: sessionId,
194
+ })
195
+
196
+ return state
197
+ } catch (error: any) {
198
+ logger.warn("Failed to load session state", {
199
+ sessionId: sessionId,
200
+ error: error?.message,
201
+ })
202
+ return null
203
+ }
204
+ }
205
+
206
+ export interface AggregatedStats {
207
+ totalTokens: number
208
+ totalTools: number
209
+ totalMessages: number
210
+ sessionCount: number
211
+ }
212
+
213
+ export async function loadAllSessionStats(logger: Logger): Promise<AggregatedStats> {
214
+ const result: AggregatedStats = {
215
+ totalTokens: 0,
216
+ totalTools: 0,
217
+ totalMessages: 0,
218
+ sessionCount: 0,
219
+ }
220
+
221
+ try {
222
+ if (!existsSync(STORAGE_DIR)) {
223
+ return result
224
+ }
225
+
226
+ const files = await fs.readdir(STORAGE_DIR)
227
+ const jsonFiles = files.filter((f) => f.endsWith(".json"))
228
+
229
+ for (const file of jsonFiles) {
230
+ try {
231
+ const filePath = join(STORAGE_DIR, file)
232
+ const content = await fs.readFile(filePath, "utf-8")
233
+ const state = JSON.parse(content) as PersistedSessionState
234
+
235
+ if (state?.stats?.totalPruneTokens && state?.prune) {
236
+ result.totalTokens += state.stats.totalPruneTokens
237
+ result.totalTools += state.prune.tools
238
+ ? Object.keys(state.prune.tools).length
239
+ : 0
240
+ result.totalMessages += state.prune.messages?.byMessageId
241
+ ? Object.keys(state.prune.messages.byMessageId).length
242
+ : 0
243
+ result.sessionCount++
244
+ }
245
+ } catch {
246
+ // Skip invalid files
247
+ }
248
+ }
249
+
250
+ logger.debug("Loaded all-time stats", result)
251
+ } catch (error: any) {
252
+ logger.warn("Failed to load all-time stats", { error: error?.message })
253
+ }
254
+
255
+ return result
256
+ }
@@ -0,0 +1,190 @@
1
+ import type { SessionState, ToolParameterEntry, WithParts } from "./types"
2
+ import type { Logger } from "../logger"
3
+ import { applyPendingCompressionDurations } from "../compress/timing"
4
+ import { loadSessionState, saveSessionState } from "./persistence"
5
+ import {
6
+ isSubAgentSession,
7
+ findLastCompactionTimestamp,
8
+ countTurns,
9
+ resetOnCompaction,
10
+ createPruneMessagesState,
11
+ loadPruneMessagesState,
12
+ loadPruneMap,
13
+ collectTurnNudgeAnchors,
14
+ } from "./utils"
15
+ import { getLastUserMessage } from "../messages/query"
16
+
17
+ export const checkSession = async (
18
+ client: any,
19
+ state: SessionState,
20
+ logger: Logger,
21
+ messages: WithParts[],
22
+ manualModeDefault: boolean,
23
+ ): Promise<void> => {
24
+ const lastUserMessage = getLastUserMessage(messages)
25
+ if (!lastUserMessage) {
26
+ return
27
+ }
28
+
29
+ const lastSessionId = lastUserMessage.info.sessionID
30
+
31
+ if (state.sessionId === null || state.sessionId !== lastSessionId) {
32
+ logger.info(`Session changed: ${state.sessionId} -> ${lastSessionId}`)
33
+ try {
34
+ await ensureSessionInitialized(
35
+ client,
36
+ state,
37
+ lastSessionId,
38
+ logger,
39
+ messages,
40
+ manualModeDefault,
41
+ )
42
+ } catch (err: any) {
43
+ logger.error("Failed to initialize session state", { error: err.message })
44
+ }
45
+ }
46
+
47
+ const lastCompactionTimestamp = findLastCompactionTimestamp(messages)
48
+ if (lastCompactionTimestamp > state.lastCompaction) {
49
+ state.lastCompaction = lastCompactionTimestamp
50
+ resetOnCompaction(state)
51
+ logger.info("Detected compaction - reset stale state", {
52
+ timestamp: lastCompactionTimestamp,
53
+ })
54
+
55
+ saveSessionState(state, logger).catch((error) => {
56
+ logger.warn("Failed to persist state reset after compaction", {
57
+ error: error instanceof Error ? error.message : String(error),
58
+ })
59
+ })
60
+ }
61
+
62
+ state.currentTurn = countTurns(state, messages)
63
+ }
64
+
65
+ export function createSessionState(): SessionState {
66
+ return {
67
+ sessionId: null,
68
+ isSubAgent: false,
69
+ manualMode: false,
70
+ compressPermission: undefined,
71
+ pendingManualTrigger: null,
72
+ prune: {
73
+ tools: new Map<string, number>(),
74
+ messages: createPruneMessagesState(),
75
+ },
76
+ nudges: {
77
+ contextLimitAnchors: new Set<string>(),
78
+ turnNudgeAnchors: new Set<string>(),
79
+ iterationNudgeAnchors: new Set<string>(),
80
+ },
81
+ stats: {
82
+ pruneTokenCounter: 0,
83
+ totalPruneTokens: 0,
84
+ },
85
+ compressionTiming: {
86
+ startsByCallId: new Map<string, number>(),
87
+ pendingByCallId: new Map(),
88
+ },
89
+ toolParameters: new Map<string, ToolParameterEntry>(),
90
+ subAgentResultCache: new Map<string, string>(),
91
+ toolIdList: [],
92
+ messageIds: {
93
+ byRawId: new Map<string, string>(),
94
+ byRef: new Map<string, string>(),
95
+ nextRef: 1,
96
+ },
97
+ lastCompaction: 0,
98
+ currentTurn: 0,
99
+ variant: undefined,
100
+ modelContextLimit: undefined,
101
+ systemPromptTokens: undefined,
102
+ }
103
+ }
104
+
105
+ export function resetSessionState(state: SessionState): void {
106
+ state.sessionId = null
107
+ state.isSubAgent = false
108
+ state.manualMode = false
109
+ state.compressPermission = undefined
110
+ state.pendingManualTrigger = null
111
+ state.prune = {
112
+ tools: new Map<string, number>(),
113
+ messages: createPruneMessagesState(),
114
+ }
115
+ state.nudges = {
116
+ contextLimitAnchors: new Set<string>(),
117
+ turnNudgeAnchors: new Set<string>(),
118
+ iterationNudgeAnchors: new Set<string>(),
119
+ }
120
+ state.stats = {
121
+ pruneTokenCounter: 0,
122
+ totalPruneTokens: 0,
123
+ }
124
+ state.toolParameters.clear()
125
+ state.subAgentResultCache.clear()
126
+ state.toolIdList = []
127
+ state.messageIds = {
128
+ byRawId: new Map<string, string>(),
129
+ byRef: new Map<string, string>(),
130
+ nextRef: 1,
131
+ }
132
+ state.lastCompaction = 0
133
+ state.currentTurn = 0
134
+ state.variant = undefined
135
+ state.modelContextLimit = undefined
136
+ state.systemPromptTokens = undefined
137
+ }
138
+
139
+ export async function ensureSessionInitialized(
140
+ client: any,
141
+ state: SessionState,
142
+ sessionId: string,
143
+ logger: Logger,
144
+ messages: WithParts[],
145
+ manualModeEnabled: boolean,
146
+ ): Promise<void> {
147
+ if (state.sessionId === sessionId) {
148
+ return
149
+ }
150
+
151
+ // logger.info("session ID = " + sessionId)
152
+ // logger.info("Initializing session state", { sessionId: sessionId })
153
+
154
+ resetSessionState(state)
155
+ state.manualMode = manualModeEnabled ? "active" : false
156
+ state.sessionId = sessionId
157
+
158
+ const isSubAgent = await isSubAgentSession(client, sessionId)
159
+ state.isSubAgent = isSubAgent
160
+ // logger.info("isSubAgent = " + isSubAgent)
161
+
162
+ state.lastCompaction = findLastCompactionTimestamp(messages)
163
+ state.currentTurn = countTurns(state, messages)
164
+ state.nudges.turnNudgeAnchors = collectTurnNudgeAnchors(messages)
165
+
166
+ const persisted = await loadSessionState(sessionId, logger)
167
+ if (persisted === null) {
168
+ return
169
+ }
170
+
171
+ state.prune.tools = loadPruneMap(persisted.prune.tools)
172
+ state.prune.messages = loadPruneMessagesState(persisted.prune.messages)
173
+ state.nudges.contextLimitAnchors = new Set<string>(persisted.nudges.contextLimitAnchors || [])
174
+ state.nudges.turnNudgeAnchors = new Set<string>([
175
+ ...state.nudges.turnNudgeAnchors,
176
+ ...(persisted.nudges.turnNudgeAnchors || []),
177
+ ])
178
+ state.nudges.iterationNudgeAnchors = new Set<string>(
179
+ persisted.nudges.iterationNudgeAnchors || [],
180
+ )
181
+ state.stats = {
182
+ pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0,
183
+ totalPruneTokens: persisted.stats?.totalPruneTokens || 0,
184
+ }
185
+
186
+ const applied = applyPendingCompressionDurations(state)
187
+ if (applied > 0) {
188
+ await saveSessionState(state, logger)
189
+ }
190
+ }
@@ -0,0 +1,98 @@
1
+ import type { SessionState, ToolStatus, WithParts } from "./index"
2
+ import type { Logger } from "../logger"
3
+ import { PluginConfig } from "../config"
4
+ import { isMessageCompacted } from "./utils"
5
+ import { countToolTokens } from "../token-utils"
6
+
7
+ const MAX_TOOL_CACHE_SIZE = 1000
8
+
9
+ /**
10
+ * Sync tool parameters from session messages.
11
+ */
12
+ export function syncToolCache(
13
+ state: SessionState,
14
+ config: PluginConfig,
15
+ logger: Logger,
16
+ messages: WithParts[],
17
+ ): void {
18
+ try {
19
+ logger.info("Syncing tool parameters from OpenCode messages")
20
+
21
+ let turnCounter = 0
22
+
23
+ for (const msg of messages) {
24
+ if (isMessageCompacted(state, msg)) {
25
+ continue
26
+ }
27
+
28
+ const parts = Array.isArray(msg.parts) ? msg.parts : []
29
+ for (const part of parts) {
30
+ if (part.type === "step-start") {
31
+ turnCounter++
32
+ continue
33
+ }
34
+
35
+ if (part.type !== "tool" || !part.callID) {
36
+ continue
37
+ }
38
+
39
+ const turnProtectionEnabled = config.turnProtection.enabled
40
+ const turnProtectionTurns = config.turnProtection.turns
41
+ const isProtectedByTurn =
42
+ turnProtectionEnabled &&
43
+ turnProtectionTurns > 0 &&
44
+ state.currentTurn - turnCounter < turnProtectionTurns
45
+
46
+ if (state.toolParameters.has(part.callID)) {
47
+ continue
48
+ }
49
+
50
+ if (isProtectedByTurn) {
51
+ continue
52
+ }
53
+
54
+ const tokenCount = countToolTokens(part)
55
+
56
+ state.toolParameters.set(part.callID, {
57
+ tool: part.tool,
58
+ parameters: part.state?.input ?? {},
59
+ status: part.state.status as ToolStatus | undefined,
60
+ error: part.state.status === "error" ? part.state.error : undefined,
61
+ turn: turnCounter,
62
+ tokenCount,
63
+ })
64
+ logger.info(
65
+ `Cached tool id: ${part.callID} (turn ${turnCounter}${tokenCount !== undefined ? `, ${tokenCount} tokens` : ""})`,
66
+ )
67
+ }
68
+ }
69
+
70
+ logger.info(
71
+ `Synced cache - size: ${state.toolParameters.size}, currentTurn: ${state.currentTurn}`,
72
+ )
73
+ trimToolParametersCache(state)
74
+ } catch (error) {
75
+ logger.warn("Failed to sync tool parameters from OpenCode", {
76
+ error: error instanceof Error ? error.message : String(error),
77
+ })
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Trim the tool parameters cache to prevent unbounded memory growth.
83
+ * Uses FIFO eviction - removes oldest entries first.
84
+ */
85
+ export function trimToolParametersCache(state: SessionState): void {
86
+ if (state.toolParameters.size <= MAX_TOOL_CACHE_SIZE) {
87
+ return
88
+ }
89
+
90
+ const keysToRemove = Array.from(state.toolParameters.keys()).slice(
91
+ 0,
92
+ state.toolParameters.size - MAX_TOOL_CACHE_SIZE,
93
+ )
94
+
95
+ for (const key of keysToRemove) {
96
+ state.toolParameters.delete(key)
97
+ }
98
+ }
@@ -0,0 +1,112 @@
1
+ import type { CompressionTimingState } from "../compress/timing"
2
+ import { Message, Part } from "@opencode-ai/sdk/v2"
3
+
4
+ export interface WithParts {
5
+ info: Message
6
+ parts: Part[]
7
+ }
8
+
9
+ export type ToolStatus = "pending" | "running" | "completed" | "error"
10
+
11
+ export interface ToolParameterEntry {
12
+ tool: string
13
+ parameters: any
14
+ status?: ToolStatus
15
+ error?: string
16
+ turn: number
17
+ tokenCount?: number
18
+ }
19
+
20
+ export interface SessionStats {
21
+ pruneTokenCounter: number
22
+ totalPruneTokens: number
23
+ }
24
+
25
+ export interface PrunedMessageEntry {
26
+ tokenCount: number
27
+ allBlockIds: number[]
28
+ activeBlockIds: number[]
29
+ }
30
+
31
+ export type CompressionMode = "range" | "message"
32
+
33
+ export interface CompressionBlock {
34
+ blockId: number
35
+ runId: number
36
+ active: boolean
37
+ deactivatedByUser: boolean
38
+ compressedTokens: number
39
+ summaryTokens: number
40
+ durationMs: number
41
+ mode?: CompressionMode
42
+ topic: string
43
+ batchTopic?: string
44
+ startId: string
45
+ endId: string
46
+ anchorMessageId: string
47
+ compressMessageId: string
48
+ compressCallId?: string
49
+ includedBlockIds: number[]
50
+ consumedBlockIds: number[]
51
+ parentBlockIds: number[]
52
+ directMessageIds: string[]
53
+ directToolIds: string[]
54
+ effectiveMessageIds: string[]
55
+ effectiveToolIds: string[]
56
+ createdAt: number
57
+ deactivatedAt?: number
58
+ deactivatedByBlockId?: number
59
+ summary: string
60
+ }
61
+
62
+ export interface PruneMessagesState {
63
+ byMessageId: Map<string, PrunedMessageEntry>
64
+ blocksById: Map<number, CompressionBlock>
65
+ activeBlockIds: Set<number>
66
+ activeByAnchorMessageId: Map<string, number>
67
+ nextBlockId: number
68
+ nextRunId: number
69
+ }
70
+
71
+ export interface Prune {
72
+ tools: Map<string, number>
73
+ messages: PruneMessagesState
74
+ }
75
+
76
+ export interface PendingManualTrigger {
77
+ sessionId: string
78
+ prompt: string
79
+ }
80
+
81
+ export interface MessageIdState {
82
+ byRawId: Map<string, string>
83
+ byRef: Map<string, string>
84
+ nextRef: number
85
+ }
86
+
87
+ export interface Nudges {
88
+ contextLimitAnchors: Set<string>
89
+ turnNudgeAnchors: Set<string>
90
+ iterationNudgeAnchors: Set<string>
91
+ }
92
+
93
+ export interface SessionState {
94
+ sessionId: string | null
95
+ isSubAgent: boolean
96
+ manualMode: false | "active" | "compress-pending"
97
+ compressPermission: "ask" | "allow" | "deny" | undefined
98
+ pendingManualTrigger: PendingManualTrigger | null
99
+ prune: Prune
100
+ nudges: Nudges
101
+ stats: SessionStats
102
+ compressionTiming: CompressionTimingState
103
+ toolParameters: Map<string, ToolParameterEntry>
104
+ subAgentResultCache: Map<string, string>
105
+ toolIdList: string[]
106
+ messageIds: MessageIdState
107
+ lastCompaction: number
108
+ currentTurn: number
109
+ variant: string | undefined
110
+ modelContextLimit: number | undefined
111
+ systemPromptTokens: number | undefined
112
+ }