diff --git a/packages/opencode/src/cli/cmd/tui/plugin.ts b/packages/opencode/src/cli/cmd/tui/plugin.ts index ceae72c43c..9b5d24f3a3 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin.ts @@ -18,7 +18,7 @@ import { resolvePluginTarget, uniqueModuleEntries } from "@/plugin/shared" import { registerThemes } from "./context/theme" type Slot = (props: { name: K } & TuiSlotMap[K]) => JSX.Element | null -type InitInput = Omit, "slots"> +type InitInput = Omit, "slots"> function empty(_props: { name: K } & TuiSlotMap[K]) { return null @@ -44,22 +44,16 @@ function getThemes(value: unknown) { return value.themes } -function isTuiPlugin(value: unknown): value is TuiPluginFn { +function isTuiPlugin(value: unknown): value is TuiPluginFn { return typeof value === "function" } -function getTuiPlugin(value: unknown) { +function getTuiPlugin(value: unknown) { if (!isRecord(value) || !("tui" in value)) return - if (!isTuiPlugin(value.tui)) return + if (!isTuiPlugin(value.tui)) return return value.tui } -function isCliRenderer(value: unknown): value is CliRenderer { - if (!isRecord(value)) return false - if (!("once" in value)) return false - return typeof value.once === "function" -} - export namespace TuiPlugin { const log = Log.create({ service: "tui.plugin" }) let loaded: Promise | undefined @@ -67,11 +61,7 @@ export namespace TuiPlugin { export const Slot: Slot = (props) => view(props) - function setupSlots(renderer: unknown): TuiSlots { - if (!isCliRenderer(renderer)) { - throw new TypeError("Invalid TUI renderer") - } - + function setupSlots(renderer: CliRenderer): TuiSlots { const reg = createSolidSlotRegistry( renderer, {}, @@ -98,7 +88,7 @@ export namespace TuiPlugin { } } - export async function init(input: InitInput) { + export async function init(input: InitInput) { if (loaded) return loaded loaded = load({ ...input, @@ -107,7 +97,7 @@ export namespace TuiPlugin { return loaded } - async function load(input: TuiPluginInput) { + async function load(input: TuiPluginInput) { const dir = process.cwd() await Instance.provide({ @@ -148,7 +138,7 @@ export namespace TuiPlugin { const slotPlugin = getTuiSlotPlugin(entry) if (slotPlugin) input.slots.register(slotPlugin) - const tuiPlugin = getTuiPlugin(entry) + const tuiPlugin = getTuiPlugin(entry) if (!tuiPlugin) continue await tuiPlugin(input, Config.pluginOptions(item)) } diff --git a/packages/opencode/test/cli/tui/plugin-loader.test.ts b/packages/opencode/test/cli/tui/plugin-loader.test.ts index a57993a3c0..c425a91c42 100644 --- a/packages/opencode/test/cli/tui/plugin-loader.test.ts +++ b/packages/opencode/test/cli/tui/plugin-loader.test.ts @@ -3,6 +3,7 @@ import fs from "fs/promises" import path from "path" import { pathToFileURL } from "url" import { createOpencodeClient } from "@opencode-ai/sdk/v2" +import type { CliRenderer } from "@opentui/core" import { tmpdir } from "../../fixture/fixture" import { Log } from "../../../src/util/log" @@ -80,6 +81,13 @@ test("ignores function-only tui exports and loads object exports", async () => { process.env.OPENCODE_TUI_CONFIG = tmp.extra.configPath const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) + const renderer = { + ...Object.create(null), + once(this: CliRenderer) { + return this + }, + } satisfies CliRenderer + try { await TuiPlugin.init({ client: createOpencodeClient({ @@ -88,9 +96,7 @@ test("ignores function-only tui exports and loads object exports", async () => { event: { on: () => () => {}, }, - renderer: { - once: () => undefined, - }, + renderer, }) expect(await fs.readFile(tmp.extra.objMarker, "utf8")).toBe("called")