import { createStore } from "solid-js/store" import { createMemo, For, Match, Show, Switch } from "solid-js" import { useKeyboard, useTerminalDimensions, type JSX } from "@opentui/solid" import type { TextareaRenderable } from "@opentui/core" import { useKeybind } from "../../context/keybind" import { useTheme, selectedForeground } from "../../context/theme" import type { PermissionRequest } from "@opencode-ai/sdk/v2" import { useSDK } from "../../context/sdk" import { SplitBorder } from "../../component/border" import { useSync } from "../../context/sync" import { useTextareaKeybindings } from "../../component/textarea-keybindings" import path from "path" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import { Locale } from "@/util/locale" type PermissionStage = "permission" | "always" | "reject" function normalizePath(input?: string) { if (!input) return "" if (path.isAbsolute(input)) { return path.relative(process.cwd(), input) || "." } return input } function filetype(input?: string) { if (!input) return "none" const ext = path.extname(input) const language = LANGUAGE_EXTENSIONS[ext] if (["typescriptreact", "javascriptreact", "javascript"].includes(language)) return "typescript" return language } function EditBody(props: { request: PermissionRequest }) { const { theme, syntax } = useTheme() const sync = useSync() const dimensions = useTerminalDimensions() const filepath = createMemo(() => (props.request.metadata?.filepath as string) ?? "") const diff = createMemo(() => (props.request.metadata?.diff as string) ?? "") const view = createMemo(() => { const diffStyle = sync.data.config.tui?.diff_style if (diffStyle === "stacked") return "unified" return dimensions().width > 120 ? "split" : "unified" }) const ft = createMemo(() => filetype(filepath())) return ( {"→"} Edit {normalizePath(filepath())} ) } function TextBody(props: { title: string; description?: string; icon?: string }) { const { theme } = useTheme() return ( <> {props.icon} {props.title} {props.description} ) } export function PermissionPrompt(props: { request: PermissionRequest }) { const sdk = useSDK() const sync = useSync() const [store, setStore] = createStore({ stage: "permission" as PermissionStage, }) const session = createMemo(() => sync.data.session.find((s) => s.id === props.request.sessionID)) const input = createMemo(() => { const tool = props.request.tool if (!tool) return {} const parts = sync.data.part[tool.messageID] ?? [] for (const part of parts) { if (part.type === "tool" && part.callID === tool.callID && part.state.status !== "pending") { return part.state.input ?? {} } } return {} }) const { theme } = useTheme() return ( This will allow the following patterns until Tinfoil is restarted {(pattern) => ( {"- "} {pattern} )} } options={{ confirm: "Confirm", cancel: "Cancel" }} escapeKey="cancel" onSelect={(option) => { setStore("stage", "permission") if (option === "cancel") return sdk.client.permission.reply({ reply: "always", requestID: props.request.id, }) }} /> { sdk.client.permission.reply({ reply: "reject", requestID: props.request.id, message: message || undefined, }) }} onCancel={() => setStore("stage", "permission")} /> } options={{ once: "Allow once", always: "Allow always", reject: "Reject" }} escapeKey="reject" onSelect={(option) => { if (option === "always") { setStore("stage", "always") return } if (option === "reject") { if (session()?.parentID) { setStore("stage", "reject") return } sdk.client.permission.reply({ reply: "reject", requestID: props.request.id, }) } sdk.client.permission.reply({ reply: "once", requestID: props.request.id, }) }} /> ) } function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: () => void }) { let input: TextareaRenderable const { theme } = useTheme() const keybind = useKeybind() const textareaKeybindings = useTextareaKeybindings() useKeyboard((evt) => { if (evt.name === "escape" || keybind.match("app_exit", evt)) { evt.preventDefault() props.onCancel() return } if (evt.name === "return") { evt.preventDefault() props.onConfirm(input.plainText) } }) return ( {"△"} Reject permission Tell Tinfoil what to do differently