@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,334 +0,0 @@
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
- }
@@ -1,162 +0,0 @@
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
- }
@@ -1,177 +0,0 @@
1
- import { Logger } from "../../lib/logger"
2
- import {
3
- createSessionState,
4
- loadSessionState,
5
- type SessionState,
6
- type WithParts,
7
- } from "../../lib/state"
8
- import {
9
- findLastCompactionTimestamp,
10
- getActiveSummaryTokenUsage,
11
- loadPruneMap,
12
- loadPruneMessagesState,
13
- } from "../../lib/state/utils"
14
- import { loadAllSessionStats } from "../../lib/state/persistence"
15
- import { analyzeTokens, emptyBreakdown } from "../../lib/analysis/tokens"
16
- import type { DcpContextSnapshot, DcpTuiClient } from "../shared/types"
17
-
18
- const snapshotCache = new Map<string, DcpContextSnapshot>()
19
- const inflightSnapshots = new Map<string, Promise<DcpContextSnapshot>>()
20
- const CACHE_TTL_MS = 5000
21
-
22
- export const createPlaceholderContextSnapshot = (
23
- sessionID?: string,
24
- notes: string[] = [],
25
- ): DcpContextSnapshot => ({
26
- sessionID,
27
- breakdown: emptyBreakdown(),
28
- activeSummaryTokens: 0,
29
- persisted: {
30
- available: false,
31
- activeBlockCount: 0,
32
- activeBlocks: [],
33
- },
34
- messageStatuses: [],
35
- allTimeStats: { totalTokensSaved: 0, sessionCount: 0 },
36
- notes,
37
- loadedAt: Date.now(),
38
- })
39
-
40
- function cleanBlockSummary(raw: string): string {
41
- return raw
42
- .replace(/^\s*\[Compressed conversation section\]\s*/i, "")
43
- .replace(/(?:\r?\n)*<dcp-message-id>b\d+<\/dcp-message-id>\s*$/i, "")
44
- .trim()
45
- }
46
-
47
- const buildState = async (
48
- sessionID: string,
49
- messages: WithParts[],
50
- logger: Logger,
51
- ): Promise<{ state: SessionState; persisted: Awaited<ReturnType<typeof loadSessionState>> }> => {
52
- const state = createSessionState()
53
- const persisted = await loadSessionState(sessionID, logger)
54
-
55
- state.sessionId = sessionID
56
- state.lastCompaction = findLastCompactionTimestamp(messages)
57
- state.stats.pruneTokenCounter = 0
58
- state.stats.totalPruneTokens = persisted?.stats?.totalPruneTokens || 0
59
- state.prune.tools = loadPruneMap(persisted?.prune?.tools)
60
- state.prune.messages = loadPruneMessagesState(persisted?.prune?.messages)
61
-
62
- return {
63
- state,
64
- persisted,
65
- }
66
- }
67
-
68
- const loadContextSnapshot = async (
69
- client: DcpTuiClient,
70
- logger: Logger,
71
- sessionID?: string,
72
- ): Promise<DcpContextSnapshot> => {
73
- if (!sessionID) {
74
- return createPlaceholderContextSnapshot(undefined, ["No active session."])
75
- }
76
-
77
- const messagesResult = await client.session.messages({ sessionID })
78
- const rawMessages =
79
- messagesResult && typeof messagesResult === "object" && "data" in messagesResult
80
- ? messagesResult.data
81
- : messagesResult
82
- const messages = Array.isArray(rawMessages) ? (rawMessages as WithParts[]) : ([] as WithParts[])
83
-
84
- const { state, persisted } = await buildState(sessionID, messages, logger)
85
- const [{ breakdown, messageStatuses }, aggregated] = await Promise.all([
86
- Promise.resolve(analyzeTokens(state, messages)),
87
- loadAllSessionStats(logger),
88
- ])
89
-
90
- const allBlocks = Array.from(state.prune.messages.activeBlockIds)
91
- .map((blockID) => state.prune.messages.blocksById.get(blockID))
92
- .filter((block): block is NonNullable<typeof block> => !!block && !!block.topic)
93
- .map((block) => ({ topic: block.topic, summary: cleanBlockSummary(block.summary) }))
94
-
95
- const notes: string[] = []
96
- if (persisted) {
97
- notes.push("Using live session messages plus persisted DCP state.")
98
- } else {
99
- notes.push("No saved DCP state found for this session yet.")
100
- }
101
- if (messages.length === 0) {
102
- notes.push("This session does not have any messages yet.")
103
- }
104
-
105
- return {
106
- sessionID,
107
- breakdown,
108
- activeSummaryTokens: getActiveSummaryTokenUsage(state),
109
- persisted: {
110
- available: !!persisted,
111
- activeBlockCount: state.prune.messages.activeBlockIds.size,
112
- activeBlocks: allBlocks,
113
- lastUpdated: persisted?.lastUpdated,
114
- },
115
- messageStatuses,
116
- allTimeStats: {
117
- totalTokensSaved: aggregated.totalTokens,
118
- sessionCount: aggregated.sessionCount,
119
- },
120
- notes,
121
- loadedAt: Date.now(),
122
- }
123
- }
124
-
125
- export const peekContextSnapshot = (sessionID?: string): DcpContextSnapshot | undefined => {
126
- if (!sessionID) return undefined
127
- return snapshotCache.get(sessionID)
128
- }
129
-
130
- export const invalidateContextSnapshot = (sessionID?: string) => {
131
- if (!sessionID) {
132
- snapshotCache.clear()
133
- inflightSnapshots.clear()
134
- return
135
- }
136
- snapshotCache.delete(sessionID)
137
- inflightSnapshots.delete(sessionID)
138
- }
139
-
140
- export const loadContextSnapshotCached = async (
141
- client: DcpTuiClient,
142
- logger: Logger,
143
- sessionID?: string,
144
- ): Promise<DcpContextSnapshot> => {
145
- if (!sessionID) {
146
- return createPlaceholderContextSnapshot(undefined, ["No active session."])
147
- }
148
-
149
- const cached = snapshotCache.get(sessionID)
150
- if (cached && Date.now() - cached.loadedAt < CACHE_TTL_MS) {
151
- return cached
152
- }
153
-
154
- const inflight = inflightSnapshots.get(sessionID)
155
- if (inflight) {
156
- return inflight
157
- }
158
-
159
- const request = loadContextSnapshot(client, logger, sessionID)
160
- .then((snapshot) => {
161
- snapshotCache.set(sessionID, snapshot)
162
- return snapshot
163
- })
164
- .catch((error) => {
165
- logger.error("Failed to load TUI context snapshot", {
166
- sessionID,
167
- error: error instanceof Error ? error.message : String(error),
168
- })
169
- throw error
170
- })
171
- .finally(() => {
172
- inflightSnapshots.delete(sessionID)
173
- })
174
-
175
- inflightSnapshots.set(sessionID, request)
176
- return request
177
- }
package/tui/index.tsx DELETED
@@ -1,34 +0,0 @@
1
- /** @jsxImportSource @opentui/solid */
2
- import type { TuiPlugin } from "@opencode-ai/plugin/tui"
3
- import { getConfigForDirectory } from "../lib/config"
4
- import { Logger } from "../lib/logger"
5
- import { createSidebarContentSlot } from "./slots/sidebar-content"
6
- import { createSummaryRoute } from "./routes/summary"
7
- import { NAMES } from "./shared/names"
8
-
9
- const tui: TuiPlugin = async (api) => {
10
- const config = getConfigForDirectory(api.state.path.directory, (title, message) => {
11
- api.ui.toast({
12
- title,
13
- message,
14
- variant: "warning",
15
- duration: 7000,
16
- })
17
- })
18
- if (!config.enabled) return
19
-
20
- const logger = new Logger(config.tui.debug, "tui")
21
-
22
- api.route.register([createSummaryRoute(api)])
23
-
24
- if (config.tui.sidebar) {
25
- api.slots.register(createSidebarContentSlot(api, NAMES, logger))
26
- }
27
- }
28
-
29
- const id = "opencode-dynamic-context-pruning"
30
-
31
- export default {
32
- id,
33
- tui,
34
- }