Skip to content

Commit 0dbdc88

Browse files
committed
refactor(tui): align plugin with flat TuiPluginApi and updated slot/route contracts
1 parent e6e2e9e commit 0dbdc88

8 files changed

Lines changed: 109 additions & 61 deletions

File tree

index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
} from "./lib/hooks"
1919
import { configureClientAuth, isSecureMode } from "./lib/auth"
2020

21+
const id = "opencode-dynamic-context-pruning"
22+
2123
let tuiPlugin: Record<string, unknown> = {}
2224
try {
2325
tuiPlugin = (await import("./tui/index")).default
@@ -138,6 +140,7 @@ const server: Plugin = (async (ctx) => {
138140
}) satisfies Plugin
139141

140142
export default {
143+
id,
141144
server,
142145
...tuiPlugin,
143146
}

tui/data/context.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const createPlaceholderContextSnapshot = (
3838
function cleanBlockSummary(raw: string): string {
3939
return raw
4040
.replace(/^\s*\[Compressed conversation section\]\s*/i, "")
41-
.replace(/\s*<dcp-message-id>b\d+<\/dcp-message-id>\s*$/i, "")
41+
.replace(/\s*b\d+<\/dcp-message-id>\s*$/i, "")
4242
.trim()
4343
}
4444

@@ -73,9 +73,11 @@ const loadContextSnapshot = async (
7373
}
7474

7575
const messagesResult = await client.session.messages({ sessionID })
76-
const messages = Array.isArray(messagesResult.data)
77-
? (messagesResult.data as WithParts[])
78-
: ([] as WithParts[])
76+
const rawMessages =
77+
messagesResult && typeof messagesResult === "object" && "data" in messagesResult
78+
? messagesResult.data
79+
: messagesResult
80+
const messages = Array.isArray(rawMessages) ? (rawMessages as WithParts[]) : ([] as WithParts[])
7981

8082
const { state, persisted } = await buildState(sessionID, messages, logger)
8183
const [{ breakdown, messageStatuses }, aggregated] = await Promise.all([

tui/dcp-probe.tsx

Lines changed: 0 additions & 1 deletion
This file was deleted.

tui/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import type { TuiPlugin } from "@opencode-ai/plugin/tui"
33
import { getConfigForDirectory } from "../lib/config"
44
import { Logger } from "../lib/logger"
5-
import { createSidebarTopSlot } from "./slots/sidebar-top"
5+
import { createSidebarContentSlot } from "./slots/sidebar-content"
66
import { createSummaryRoute } from "./routes/summary"
77
import { NAMES } from "./shared/names"
88

@@ -22,10 +22,13 @@ const tui: TuiPlugin = async (api) => {
2222
api.route.register([createSummaryRoute(api)])
2323

2424
if (config.tui.sidebar) {
25-
api.slots.register(createSidebarTopSlot(api, NAMES, logger))
25+
api.slots.register(createSidebarContentSlot(api, NAMES, logger))
2626
}
2727
}
2828

29+
const id = "opencode-dynamic-context-pruning"
30+
2931
export default {
32+
id,
3033
tui,
3134
}

tui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://json.schemastore.org/package.json",
3-
"name": "dcp-tui-probe-local",
3+
"name": "dcp-tui-local",
44
"private": true,
55
"type": "module",
66
"dependencies": {

tui/routes/summary.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @jsxImportSource @opentui/solid */
22
import { createMemo, createSignal, For, Show } from "solid-js"
33
import { useKeyboard } from "@opentui/solid"
4-
import type { TuiApi } from "@opencode-ai/plugin/tui"
4+
import type { TuiPluginApi } from "@opencode-ai/plugin/tui"
55
import { getPalette, type DcpPalette } from "../shared/theme"
66
import { LABEL, NAMES } from "../shared/names"
77

@@ -23,6 +23,9 @@ interface ParsedSummary {
2323
sections: CollapsibleSection[]
2424
}
2525

26+
// These patterns must stay in sync with the exact headings produced by
27+
// lib/compress (compactSummary output). If compression wording changes,
28+
// update these patterns accordingly.
2629
const SECTION_HEADINGS: { pattern: RegExp; label: string }[] = [
2730
{
2831
pattern: /\n*The following user messages were sent in this conversation verbatim:/,
@@ -99,13 +102,9 @@ function CollapsibleSectionRow(props: { section: CollapsibleSection; palette: Dc
99102
)
100103
}
101104

102-
function SummaryScreen(props: { api: TuiApi }) {
103-
const params = createMemo(() => {
104-
const current = props.api.route.current
105-
return ("params" in current ? current.params : {}) as SummaryRouteParams
106-
})
105+
function SummaryScreen(props: { api: TuiPluginApi; params: SummaryRouteParams }) {
107106
const palette = createMemo(() => getPalette(props.api.theme.current as Record<string, unknown>))
108-
const parsed = createMemo(() => parseSummary(params().summary || ""))
107+
const parsed = createMemo(() => parseSummary(props.params.summary || ""))
109108

110109
const keys = props.api.keybind.create({ close: "escape" })
111110

@@ -116,7 +115,7 @@ function SummaryScreen(props: { api: TuiApi }) {
116115
if (!matched) return
117116
evt.preventDefault()
118117
evt.stopPropagation()
119-
const sessionID = params().sessionID
118+
const sessionID = props.params.sessionID
120119
if (sessionID) {
121120
props.api.route.navigate("session", { sessionID })
122121
} else {
@@ -139,7 +138,7 @@ function SummaryScreen(props: { api: TuiApi }) {
139138
</text>
140139
</box>
141140
<text fg={palette().accent}>
142-
<b>{params().topic || "Compression Summary"}</b>
141+
<b>{props.params.topic || "Compression Summary"}</b>
143142
</text>
144143
</box>
145144

@@ -168,7 +167,9 @@ function SummaryScreen(props: { api: TuiApi }) {
168167
)
169168
}
170169

171-
export const createSummaryRoute = (api: TuiApi) => ({
170+
export const createSummaryRoute = (api: TuiPluginApi) => ({
172171
name: NAMES.routes.summary,
173-
render: () => <SummaryScreen api={api} />,
172+
render: (input: { params?: Record<string, unknown> }) => (
173+
<SummaryScreen api={api} params={(input.params ?? {}) as SummaryRouteParams} />
174+
),
174175
})
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ const SidebarContext = (props: {
467467
)
468468
}
469469

470-
export const createSidebarTopSlot = (
470+
export const createSidebarContentSlot = (
471471
api: DcpTuiApi,
472472
names: DcpRouteNames,
473473
logger: Logger,

tui/types/opencode-plugin-tui.d.ts

Lines changed: 81 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,16 @@ declare module "@opencode-ai/plugin/tui" {
8181
}
8282

8383
export type TuiDialogProps = {
84-
size?: "medium" | "large"
84+
size?: "medium" | "large" | "xlarge"
8585
onClose: () => void
8686
children?: JSX.Element
8787
}
8888

8989
export type TuiDialogStack = {
9090
replace: (render: () => JSX.Element, onClose?: () => void) => void
9191
clear: () => void
92-
setSize: (size: "medium" | "large") => void
93-
readonly size: "medium" | "large"
92+
setSize: (size: "medium" | "large" | "xlarge") => void
93+
readonly size: "medium" | "large" | "xlarge"
9494
readonly depth: number
9595
readonly open: boolean
9696
}
@@ -247,9 +247,17 @@ declare module "@opencode-ai/plugin/tui" {
247247
mcp: () => ReadonlyArray<TuiSidebarMcpItem>
248248
}
249249

250-
// Inlined: Pick<PluginConfig, "$schema" | "theme" | "keybinds" | "plugin"> & NonNullable<PluginConfig["tui"]>
250+
// Upstream: Pick<PluginConfig, "$schema" | "theme" | "keybinds" | "plugin"> & NonNullable<PluginConfig["tui"]> & { plugin_enabled?: Record<string, boolean> }
251251
// PluginConfig (opencode.json schema) is not re-exported by @opencode-ai/plugin at installed version.
252-
type TuiConfigView = Record<string, unknown>
252+
// Approximation using known TUI-native config keys:
253+
type TuiConfigView = {
254+
$schema?: string
255+
theme?: string
256+
keybinds?: Record<string, string>
257+
plugin?: Record<string, unknown>
258+
plugin_enabled?: Record<string, boolean>
259+
[key: string]: unknown
260+
}
253261

254262
type Frozen<Value> = Value extends (...args: never[]) => unknown
255263
? Value
@@ -263,37 +271,6 @@ declare module "@opencode-ai/plugin/tui" {
263271
readonly version: string
264272
}
265273

266-
export type TuiApi = {
267-
app: TuiApp
268-
command: {
269-
register: (cb: () => TuiCommand[]) => () => void
270-
trigger: (value: string) => void
271-
}
272-
route: {
273-
register: (routes: TuiRouteDefinition[]) => () => void
274-
navigate: (name: string, params?: Record<string, unknown>) => void
275-
readonly current: TuiRouteCurrent
276-
}
277-
ui: {
278-
Dialog: (props: TuiDialogProps) => JSX.Element
279-
DialogAlert: (props: TuiDialogAlertProps) => JSX.Element
280-
DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element
281-
DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element
282-
DialogSelect: <Value = unknown>(props: TuiDialogSelectProps<Value>) => JSX.Element
283-
toast: (input: TuiToast) => void
284-
dialog: TuiDialogStack
285-
}
286-
keybind: {
287-
match: (key: string, evt: ParsedKey) => boolean
288-
print: (key: string) => string
289-
create: (defaults: TuiKeybindMap, overrides?: Record<string, unknown>) => TuiKeybindSet
290-
}
291-
readonly tuiConfig: Frozen<TuiConfigView>
292-
kv: TuiKV
293-
state: TuiState
294-
theme: TuiTheme
295-
}
296-
297274
export type TuiSidebarMcpItem = {
298275
name: string
299276
status: McpStatus["status"]
@@ -356,7 +333,7 @@ declare module "@opencode-ai/plugin/tui" {
356333
export type TuiPluginState = "first" | "updated" | "same"
357334

358335
export type TuiPluginEntry = {
359-
name: string
336+
id: string
360337
source: "file" | "npm" | "internal"
361338
spec: string
362339
target: string
@@ -374,21 +351,84 @@ declare module "@opencode-ai/plugin/tui" {
374351
state: TuiPluginState
375352
}
376353

354+
export type TuiPluginStatus = {
355+
id: string
356+
source: "file" | "npm" | "internal"
357+
spec: string
358+
target: string
359+
enabled: boolean
360+
active: boolean
361+
}
362+
363+
export type TuiPluginInstallOptions = {
364+
global?: boolean
365+
}
366+
367+
export type TuiPluginInstallResult =
368+
| {
369+
ok: true
370+
dir: string
371+
tui: boolean
372+
}
373+
| {
374+
ok: false
375+
message: string
376+
missing?: boolean
377+
}
378+
377379
export type TuiWorkspace = {
378380
current: () => string | undefined
379381
set: (workspaceID?: string) => void
380382
}
381383

382-
export type TuiHostPluginApi<Renderer = CliRenderer> = TuiApi & {
384+
// Flat TuiPluginApi matching upstream spec from PR #19347.
385+
// Previous local shim split this into TuiApi -> TuiHostPluginApi -> TuiPluginApi;
386+
// the upstream API is a single flat type.
387+
export type TuiPluginApi<Renderer = CliRenderer> = {
388+
app: TuiApp
389+
command: {
390+
register: (cb: () => TuiCommand[]) => () => void
391+
trigger: (value: string) => void
392+
}
393+
route: {
394+
register: (routes: TuiRouteDefinition[]) => () => void
395+
navigate: (name: string, params?: Record<string, unknown>) => void
396+
readonly current: TuiRouteCurrent
397+
}
398+
ui: {
399+
Dialog: (props: TuiDialogProps) => JSX.Element
400+
DialogAlert: (props: TuiDialogAlertProps) => JSX.Element
401+
DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element
402+
DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element
403+
DialogSelect: <Value = unknown>(props: TuiDialogSelectProps<Value>) => JSX.Element
404+
toast: (input: TuiToast) => void
405+
dialog: TuiDialogStack
406+
}
407+
keybind: {
408+
match: (key: string, evt: ParsedKey) => boolean
409+
print: (key: string) => string
410+
create: (defaults: TuiKeybindMap, overrides?: Record<string, unknown>) => TuiKeybindSet
411+
}
412+
readonly tuiConfig: Frozen<TuiConfigView>
413+
kv: TuiKV
414+
state: TuiState
415+
theme: TuiTheme
383416
client: ReturnType<typeof createOpencodeClientV2>
384417
scopedClient: (workspaceID?: string) => ReturnType<typeof createOpencodeClientV2>
385418
workspace: TuiWorkspace
386419
event: TuiEventBus
387420
renderer: Renderer
388-
}
389-
390-
export type TuiPluginApi<Renderer = CliRenderer> = TuiHostPluginApi<Renderer> & {
391421
slots: TuiSlots
422+
plugins: {
423+
list: () => ReadonlyArray<TuiPluginStatus>
424+
activate: (id: string) => Promise<boolean>
425+
deactivate: (id: string) => Promise<boolean>
426+
add: (spec: string) => Promise<boolean>
427+
install: (
428+
spec: string,
429+
options?: TuiPluginInstallOptions,
430+
) => Promise<TuiPluginInstallResult>
431+
}
392432
lifecycle: TuiLifecycle
393433
}
394434

0 commit comments

Comments
 (0)