opencode/packages/tui/src/mini/permission.shared.ts

199 lines
4.5 KiB
TypeScript

import type { MiniPermissionRequest, PermissionReply } from "./types"
import { permissionAlwaysLines, permissionOptionLabel, permissionPresentation } from "../util/permission"
import { toolPath } from "./tool"
import { monoPrefix } from "./mono"
export type PermissionStage = "permission" | "always" | "reject"
export type PermissionOption = "once" | "always" | "reject" | "confirm" | "cancel"
export type PermissionBodyState = {
requestID: string
sessionID: string
stage: PermissionStage
selected: PermissionOption
message: string
submitting: boolean
}
export type PermissionStep = {
state: PermissionBodyState
reply?: PermissionReply
}
export function createPermissionBodyState(
request: Pick<MiniPermissionRequest, "id" | "sessionID">,
): PermissionBodyState {
return {
requestID: request.id,
sessionID: request.sessionID,
stage: "permission",
selected: "once",
message: "",
submitting: false,
}
}
export function permissionOptions(stage: PermissionStage): PermissionOption[] {
if (stage === "permission") {
return ["once", "always", "reject"]
}
if (stage === "always") {
return ["confirm", "cancel"]
}
return []
}
export function permissionInfo(request: MiniPermissionRequest, directory?: string, mono = false) {
const state = request.tool?.state
const info = permissionPresentation(
{
action: request.action,
resources: request.resources,
metadata: request.metadata,
input: state?.status === "streaming" ? undefined : state?.input,
toolMetadata: state?.status === "streaming" ? undefined : state?.metadata,
},
(value) => toolPath(value, { home: true, directory }),
)
if (!mono) return info
return {
...info,
icon: monoPrefix(info.icon, true),
lines: [
...(info.diff || info.patch ? [info.diff ?? info.patch!] : []),
...info.lines.map((line) => monoPrefix(line, true)),
],
diff: undefined,
patch: undefined,
}
}
export function permissionLabel(option: PermissionOption): string {
return permissionOptionLabel(option)
}
export { permissionAlwaysLines }
function permissionReply(
sessionID: string,
requestID: string,
reply: PermissionReply["reply"],
message?: string,
): PermissionReply {
return {
sessionID,
requestID,
reply,
...(message && message.trim() ? { message: message.trim() } : {}),
}
}
export function permissionShift(
state: PermissionBodyState,
dir: -1 | 1,
list = permissionOptions(state.stage),
): PermissionBodyState {
if (list.length === 0) {
return state
}
const idx = Math.max(0, list.indexOf(state.selected))
const selected = list[(idx + dir + list.length) % list.length]
return {
...state,
selected,
}
}
export function permissionHover(state: PermissionBodyState, option: PermissionOption): PermissionBodyState {
return {
...state,
selected: option,
}
}
export function permissionRun(state: PermissionBodyState, requestID: string, option: PermissionOption): PermissionStep {
if (state.submitting) {
return { state }
}
if (state.stage === "permission") {
if (option === "always") {
return {
state: {
...state,
stage: "always",
selected: "confirm",
},
}
}
if (option === "reject") {
return {
state: {
...state,
stage: "reject",
selected: "reject",
},
}
}
return {
state,
reply: permissionReply(state.sessionID, requestID, "once"),
}
}
if (state.stage !== "always") {
return { state }
}
if (option === "cancel") {
return {
state: {
...state,
stage: "permission",
selected: "always",
},
}
}
return {
state,
reply: permissionReply(state.sessionID, requestID, "always"),
}
}
export function permissionReject(state: PermissionBodyState, requestID: string): PermissionReply | undefined {
if (state.submitting) {
return undefined
}
return permissionReply(state.sessionID, requestID, "reject", state.message)
}
export function permissionCancel(state: PermissionBodyState): PermissionBodyState {
return {
...state,
stage: "permission",
selected: "reject",
}
}
export function permissionEscape(state: PermissionBodyState): PermissionBodyState {
if (state.stage === "always") {
return {
...state,
stage: "permission",
selected: "always",
}
}
return {
...state,
stage: "reject",
selected: "reject",
}
}