| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- import type { TuiDialogSelectOption, TuiPluginApi, TuiSlotProps } from "@kirincode-ai/plugin/tui"
- import type { TuiConfig } from "../config"
- import type { useEvent } from "../context/event"
- import type { useRoute } from "../context/route"
- import type { useSDK } from "../context/sdk"
- import type { useSync } from "../context/sync"
- import type { useTheme } from "../context/theme"
- import { Dialog as DialogUI, type useDialog } from "../ui/dialog"
- import type { useOpencodeKeymap } from "../keymap"
- import type { useKV } from "../context/kv"
- import { DialogAlert } from "../ui/dialog-alert"
- import { DialogConfirm } from "../ui/dialog-confirm"
- import { DialogPrompt } from "../ui/dialog-prompt"
- import { DialogSelect, type DialogSelectOption as SelectOption } from "../ui/dialog-select"
- import { Prompt } from "../component/prompt"
- import type { useToast } from "../ui/toast"
- import * as Keymap from "../keymap"
- import { createCommandShim } from "./command-shim"
- import type { PluginRoutes } from "./api"
- export type { RouteMap } from "./api"
- export { createPluginRoutes, createTuiApi } from "./api"
- type Input = {
- version: string
- tuiConfig: TuiConfig.Resolved
- dialog: ReturnType<typeof useDialog>
- keymap: ReturnType<typeof useOpencodeKeymap>
- kv: ReturnType<typeof useKV>
- route: ReturnType<typeof useRoute>
- routes: PluginRoutes
- event: ReturnType<typeof useEvent>
- sdk: ReturnType<typeof useSDK>
- sync: ReturnType<typeof useSync>
- theme: ReturnType<typeof useTheme>
- toast: ReturnType<typeof useToast>
- renderer: TuiPluginApi["renderer"]
- attention: TuiPluginApi["attention"]
- Slot: TuiPluginApi["ui"]["Slot"]
- }
- function routeNavigate(route: ReturnType<typeof useRoute>, name: string, params?: Record<string, unknown>) {
- if (name === "home") {
- route.navigate({ type: "home" })
- return
- }
- if (name === "session") {
- const sessionID = params?.sessionID
- if (typeof sessionID !== "string") return
- route.navigate({ type: "session", sessionID })
- return
- }
- route.navigate({ type: "plugin", id: name, data: params })
- }
- function routeCurrent(route: ReturnType<typeof useRoute>): TuiPluginApi["route"]["current"] {
- if (route.data.type === "home") return { name: "home" }
- if (route.data.type === "session") {
- return {
- name: "session",
- params: {
- sessionID: route.data.sessionID,
- prompt: route.data.prompt,
- },
- }
- }
- return {
- name: route.data.id,
- params: route.data.data,
- }
- }
- function mapOption<Value>(item: TuiDialogSelectOption<Value>): SelectOption<Value> {
- return {
- ...item,
- onSelect: () => item.onSelect?.(),
- }
- }
- function pickOption<Value>(item: SelectOption<Value>): TuiDialogSelectOption<Value> {
- return {
- title: item.title,
- value: item.value,
- description: item.description,
- footer: item.footer,
- category: item.category,
- disabled: item.disabled,
- }
- }
- function mapOptionCb<Value>(cb?: (item: TuiDialogSelectOption<Value>) => void) {
- if (!cb) return
- return (item: SelectOption<Value>) => cb(pickOption(item))
- }
- function stateApi(sync: ReturnType<typeof useSync>): TuiPluginApi["state"] {
- return {
- get ready() {
- return sync.ready
- },
- get config() {
- return sync.data.config
- },
- get provider() {
- return sync.data.provider
- },
- get path() {
- return sync.path
- },
- get vcs() {
- if (!sync.data.vcs) return
- return {
- branch: sync.data.vcs.branch,
- default_branch: sync.data.vcs.default_branch,
- }
- },
- session: {
- count() {
- return sync.data.session.length
- },
- get(sessionID) {
- return sync.session.get(sessionID)
- },
- diff(sessionID) {
- return (sync.data.session_diff[sessionID] ?? []).flatMap((item) =>
- item.file === undefined ? [] : [{ ...item, file: item.file }],
- )
- },
- todo(sessionID) {
- return sync.data.todo[sessionID] ?? []
- },
- messages(sessionID) {
- return sync.data.message[sessionID] ?? []
- },
- status(sessionID) {
- return sync.data.session_status[sessionID]
- },
- permission(sessionID) {
- return sync.data.permission[sessionID] ?? []
- },
- question(sessionID) {
- return sync.data.question[sessionID] ?? []
- },
- },
- part(messageID) {
- return sync.data.part[messageID] ?? []
- },
- lsp() {
- return sync.data.lsp.map((item) => ({ id: item.id, root: item.root, status: item.status }))
- },
- mcp() {
- return Object.entries(sync.data.mcp)
- .sort(([a], [b]) => a.localeCompare(b))
- .map(([name, item]) => ({
- name,
- status: item.status,
- error: item.status === "failed" ? item.error : undefined,
- }))
- },
- }
- }
- function appApi(version: string): TuiPluginApi["app"] {
- return {
- get version() {
- return version
- },
- }
- }
- export function createTuiApiAdapters(input: Input): Omit<TuiPluginApi, "lifecycle"> {
- return {
- app: appApi(input.version),
- attention: input.attention,
- // 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)
- },
- formatBindings(bindings) {
- return Keymap.formatKeyBindings(bindings, input.tuiConfig)
- },
- },
- keymap: input.keymap,
- mode: {
- current() {
- return Keymap.getOpencodeModeStack(input.keymap).current()
- },
- push(mode) {
- return Keymap.getOpencodeModeStack(input.keymap).push(mode)
- },
- },
- route: {
- register(list) {
- return input.routes.register(list)
- },
- navigate(name, params) {
- routeNavigate(input.route, name, params)
- },
- get current() {
- return routeCurrent(input.route)
- },
- },
- ui: {
- Dialog(props) {
- return (
- <DialogUI size={props.size} onClose={props.onClose}>
- {props.children}
- </DialogUI>
- )
- },
- DialogAlert(props) {
- return <DialogAlert {...props} />
- },
- DialogConfirm(props) {
- return <DialogConfirm {...props} />
- },
- DialogPrompt(props) {
- return <DialogPrompt {...props} description={props.description} />
- },
- DialogSelect(props) {
- return (
- <DialogSelect
- title={props.title}
- placeholder={props.placeholder}
- options={props.options.map(mapOption)}
- flat={props.flat}
- onMove={mapOptionCb(props.onMove)}
- onFilter={props.onFilter}
- onSelect={mapOptionCb(props.onSelect)}
- skipFilter={props.skipFilter}
- current={props.current}
- />
- )
- },
- Slot<Name extends string>(props: TuiSlotProps<Name>) {
- return <input.Slot {...props} />
- },
- Prompt(props) {
- return (
- <Prompt
- sessionID={props.sessionID}
- visible={props.visible}
- disabled={props.disabled}
- onSubmit={props.onSubmit}
- ref={props.ref}
- hint={props.hint}
- right={props.right}
- showPlaceholder={props.showPlaceholder}
- placeholders={props.placeholders}
- />
- )
- },
- toast(inputToast) {
- input.toast.show({
- title: inputToast.title,
- message: inputToast.message,
- variant: inputToast.variant ?? "info",
- duration: inputToast.duration,
- })
- },
- dialog: {
- replace(render, onClose) {
- input.dialog.replace(render, onClose)
- },
- clear() {
- input.dialog.clear()
- },
- setSize(size) {
- input.dialog.setSize(size)
- },
- get size() {
- return input.dialog.size
- },
- get depth() {
- return input.dialog.stack.length
- },
- get open() {
- return input.dialog.stack.length > 0
- },
- },
- },
- get tuiConfig() {
- return input.tuiConfig
- },
- kv: {
- get(key, fallback) {
- return input.kv.get(key, fallback)
- },
- set(key, value) {
- input.kv.set(key, value)
- },
- get ready() {
- return input.kv.ready
- },
- },
- state: stateApi(input.sync),
- get client() {
- return input.sdk.client
- },
- event: input.event,
- renderer: input.renderer,
- slots: {
- register() {
- throw new Error("slots.register is only available in plugin context")
- },
- },
- plugins: {
- list() {
- return []
- },
- async activate() {
- return false
- },
- async deactivate() {
- return false
- },
- async add() {
- return false
- },
- async install() {
- return {
- ok: false,
- message: "plugins.install is only available in plugin context",
- }
- },
- },
- theme: {
- get current() {
- return input.theme.theme
- },
- get selected() {
- return input.theme.selected
- },
- has(name) {
- return input.theme.has(name)
- },
- set(name) {
- return input.theme.set(name)
- },
- async install(_jsonPath) {
- throw new Error("theme.install is only available in plugin context")
- },
- mode() {
- return input.theme.mode()
- },
- get ready() {
- return input.theme.ready
- },
- },
- }
- }
|