Plugin command API shim (#26564)

This commit is contained in:
Sebastian
2026-05-09 21:56:49 +02:00
committed by GitHub
parent dcdbdb218f
commit 9a8b54fe62
5 changed files with 225 additions and 0 deletions

View File

@@ -17,6 +17,7 @@ import { Slot as HostSlot } from "./slots"
import type { useToast } from "../ui/toast"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import * as Keymap from "../keymap"
import { createCommandShim } from "./command-shim"
type RouteEntry = {
key: symbol
@@ -200,6 +201,8 @@ export function createTuiApi(input: Input): TuiPluginApi {
}
return {
app: appApi(),
// Keep deprecated `api.command` working for v1 plugins; remove in v2.
command: createCommandShim(input.keymap, input.dialog, input.tuiConfig.keybinds),
keys: {
formatSequence(parts) {
return Keymap.formatKeySequence(parts, input.tuiConfig)

View File

@@ -0,0 +1,109 @@
// Legacy `api.command` bridge for v1 plugins; remove in v2.
import type { TuiCommand, TuiPluginApi } from "@opencode-ai/plugin/tui"
import { TuiKeybind } from "../config/keybind"
import type { DialogContext } from "../ui/dialog"
const COMMAND_PALETTE_SHOW = "command.palette.show"
const warned = new Set<string>()
type Warn = (api: string, replacement: string) => void
type LegacyDialog = TuiPluginApi["ui"]["dialog"]
type CommandShimDialog = DialogContext | LegacyDialog
type LegacyKeybinds = TuiPluginApi["tuiConfig"]["keybinds"]
function warnCommandShim(api: string, replacement: string) {
// Warn v1 plugins about deprecated `api.command`; remove this shim path in v2.
console.warn("[tui.plugin] deprecated TUI plugin API", { api, replacement })
}
function createCommandShimDialog(dialog: CommandShimDialog): LegacyDialog {
if (!("stack" in dialog)) return dialog
return {
replace(render, onClose) {
dialog.replace(render, onClose)
},
clear() {
dialog.clear()
},
setSize(size) {
dialog.setSize(size)
},
get size() {
return dialog.size
},
get depth() {
return dialog.stack.length
},
get open() {
return dialog.stack.length > 0
},
}
}
function warnOnce(api: string, replacement: string, warn: Warn) {
if (warned.has(api)) return
warned.add(api)
warn(api, replacement)
}
function toCommand(item: TuiCommand, dialog: LegacyDialog) {
return {
namespace: "palette",
name: item.value,
title: item.title,
desc: item.description,
category: item.category,
suggested: item.suggested,
hidden: item.hidden,
enabled: item.enabled,
slashName: item.slash?.name,
slashAliases: item.slash?.aliases,
run() {
return item.onSelect?.(dialog)
},
}
}
function toBindings(commands: TuiCommand[], keybinds: LegacyKeybinds) {
return commands.flatMap((item) =>
item.keybind
? keybinds.has(TuiKeybind.CommandMap[item.keybind as keyof typeof TuiKeybind.CommandMap] ?? item.keybind)
? keybinds
.get(TuiKeybind.CommandMap[item.keybind as keyof typeof TuiKeybind.CommandMap] ?? item.keybind)
.map((binding) => ({ ...binding, cmd: item.value, desc: binding.desc ?? item.title }))
: [
{
key: item.keybind,
cmd: item.value,
desc: item.title,
},
]
: [],
)
}
export function createCommandShim(
keymap: TuiPluginApi["keymap"],
dialog: CommandShimDialog,
keybinds: LegacyKeybinds,
): TuiPluginApi["command"] {
const shimDialog = createCommandShimDialog(dialog)
return {
register(cb) {
warnOnce("api.command.register", "api.keymap.registerLayer({ commands, bindings })", warnCommandShim)
const commands = cb()
return keymap.registerLayer({
commands: commands.map((item) => toCommand(item, shimDialog)),
bindings: toBindings(commands, keybinds),
})
},
trigger(value) {
warnOnce("api.command.trigger", "api.keymap.dispatchCommand(name)", warnCommandShim)
keymap.dispatchCommand(value)
},
show() {
warnOnce("api.command.show", `api.keymap.dispatchCommand("${COMMAND_PALETTE_SHOW}")`, warnCommandShim)
keymap.dispatchCommand(COMMAND_PALETTE_SHOW)
},
}
}

View File

@@ -39,6 +39,7 @@ import { INTERNAL_TUI_PLUGINS, type InternalTuiPlugin } from "./internal"
import { setupSlots, Slot as View } from "./slots"
import type { HostPluginApi, HostSlots } from "./slots"
import { ConfigPlugin } from "@/config/plugin"
import { createCommandShim } from "./command-shim"
ensureRuntimePluginSupport({ additional: keymapRuntimeModules })
@@ -576,6 +577,8 @@ function pluginApi(runtime: RuntimeState, plugin: PluginEntry, scope: PluginScop
return {
app: api.app,
// Keep deprecated `api.command` working for v1 plugins; remove in v2.
command: createCommandShim(keymap, api.ui.dialog, api.tuiConfig.keybinds),
keys: api.keys,
keymap,
route,