mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-29 21:33:54 +00:00
chore(permission): delete legacy permission module (#17534)
This commit is contained in:
parent
52877d8765
commit
2fc06c5a17
@ -1,210 +0,0 @@
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { Bus } from "@/bus"
|
||||
import { SessionID, MessageID } from "@/session/schema"
|
||||
import z from "zod"
|
||||
import { Log } from "../util/log"
|
||||
import { Plugin } from "../plugin"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Wildcard } from "../util/wildcard"
|
||||
import { PermissionID } from "./schema"
|
||||
|
||||
export namespace Permission {
|
||||
const log = Log.create({ service: "permission" })
|
||||
|
||||
function toKeys(pattern: Info["pattern"], type: string): string[] {
|
||||
return pattern === undefined ? [type] : Array.isArray(pattern) ? pattern : [pattern]
|
||||
}
|
||||
|
||||
function covered(keys: string[], approved: Map<string, boolean>): boolean {
|
||||
return keys.every((k) => {
|
||||
for (const p of approved.keys()) {
|
||||
if (Wildcard.match(k, p)) return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
id: PermissionID.zod,
|
||||
type: z.string(),
|
||||
pattern: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
sessionID: SessionID.zod,
|
||||
messageID: MessageID.zod,
|
||||
callID: z.string().optional(),
|
||||
message: z.string(),
|
||||
metadata: z.record(z.string(), z.any()),
|
||||
time: z.object({
|
||||
created: z.number(),
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
ref: "Permission",
|
||||
})
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
interface PendingEntry {
|
||||
info: Info
|
||||
resolve: () => void
|
||||
reject: (e: any) => void
|
||||
}
|
||||
|
||||
export const Event = {
|
||||
Updated: BusEvent.define("permission.updated", Info),
|
||||
Replied: BusEvent.define(
|
||||
"permission.replied",
|
||||
z.object({
|
||||
sessionID: SessionID.zod,
|
||||
permissionID: PermissionID.zod,
|
||||
response: z.string(),
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
const state = Instance.state(
|
||||
() => ({
|
||||
pending: new Map<SessionID, Map<PermissionID, PendingEntry>>(),
|
||||
approved: new Map<SessionID, Map<string, boolean>>(),
|
||||
}),
|
||||
async (state) => {
|
||||
for (const session of state.pending.values()) {
|
||||
for (const item of session.values()) {
|
||||
item.reject(new RejectedError(item.info.sessionID, item.info.id, item.info.callID, item.info.metadata))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export function pending() {
|
||||
return state().pending
|
||||
}
|
||||
|
||||
export function list() {
|
||||
const { pending } = state()
|
||||
const result: Info[] = []
|
||||
for (const session of pending.values()) {
|
||||
for (const item of session.values()) {
|
||||
result.push(item.info)
|
||||
}
|
||||
}
|
||||
return result.sort((a, b) => String(a.id).localeCompare(String(b.id)))
|
||||
}
|
||||
|
||||
export async function ask(input: {
|
||||
type: Info["type"]
|
||||
message: Info["message"]
|
||||
pattern?: Info["pattern"]
|
||||
callID?: Info["callID"]
|
||||
sessionID: Info["sessionID"]
|
||||
messageID: Info["messageID"]
|
||||
metadata: Info["metadata"]
|
||||
}) {
|
||||
const { pending, approved } = state()
|
||||
log.info("asking", {
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
toolCallID: input.callID,
|
||||
pattern: input.pattern,
|
||||
})
|
||||
const approvedForSession = approved.get(input.sessionID)
|
||||
const keys = toKeys(input.pattern, input.type)
|
||||
if (approvedForSession && covered(keys, approvedForSession)) return
|
||||
const info: Info = {
|
||||
id: PermissionID.ascending(),
|
||||
type: input.type,
|
||||
pattern: input.pattern,
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
callID: input.callID,
|
||||
message: input.message,
|
||||
metadata: input.metadata,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
}
|
||||
|
||||
switch (
|
||||
await Plugin.trigger("permission.ask", info, {
|
||||
status: "ask",
|
||||
}).then((x) => x.status)
|
||||
) {
|
||||
case "deny":
|
||||
throw new RejectedError(info.sessionID, info.id, info.callID, info.metadata)
|
||||
case "allow":
|
||||
return
|
||||
}
|
||||
|
||||
if (!pending.has(input.sessionID)) pending.set(input.sessionID, new Map())
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
pending.get(input.sessionID)!.set(info.id, {
|
||||
info,
|
||||
resolve,
|
||||
reject,
|
||||
})
|
||||
Bus.publish(Event.Updated, info)
|
||||
})
|
||||
}
|
||||
|
||||
export const Response = z.enum(["once", "always", "reject"])
|
||||
export type Response = z.infer<typeof Response>
|
||||
|
||||
export function respond(input: { sessionID: Info["sessionID"]; permissionID: Info["id"]; response: Response }) {
|
||||
log.info("response", input)
|
||||
const { pending, approved } = state()
|
||||
const session = pending.get(input.sessionID)
|
||||
const match = session?.get(input.permissionID)
|
||||
if (!session || !match) return
|
||||
session.delete(input.permissionID)
|
||||
if (session.size === 0) pending.delete(input.sessionID)
|
||||
Bus.publish(Event.Replied, {
|
||||
sessionID: input.sessionID,
|
||||
permissionID: input.permissionID,
|
||||
response: input.response,
|
||||
})
|
||||
if (input.response === "reject") {
|
||||
match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.callID, match.info.metadata))
|
||||
return
|
||||
}
|
||||
match.resolve()
|
||||
if (input.response === "always") {
|
||||
if (!approved.has(input.sessionID)) approved.set(input.sessionID, new Map())
|
||||
const approvedSession = approved.get(input.sessionID)!
|
||||
const approveKeys = toKeys(match.info.pattern, match.info.type)
|
||||
for (const k of approveKeys) {
|
||||
approvedSession.set(k, true)
|
||||
}
|
||||
const items = pending.get(input.sessionID)
|
||||
if (!items) return
|
||||
const toRespond: Info[] = []
|
||||
for (const item of items.values()) {
|
||||
const itemKeys = toKeys(item.info.pattern, item.info.type)
|
||||
if (covered(itemKeys, approvedSession)) {
|
||||
toRespond.push(item.info)
|
||||
}
|
||||
}
|
||||
for (const item of toRespond) {
|
||||
respond({
|
||||
sessionID: item.sessionID,
|
||||
permissionID: item.id,
|
||||
response: input.response,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RejectedError extends Error {
|
||||
constructor(
|
||||
public readonly sessionID: SessionID,
|
||||
public readonly permissionID: PermissionID,
|
||||
public readonly toolCallID?: string,
|
||||
public readonly metadata?: Record<string, any>,
|
||||
public readonly reason?: string,
|
||||
) {
|
||||
super(
|
||||
reason !== undefined
|
||||
? reason
|
||||
: `The user rejected permission to use this specific tool call. You may try again with different parameters.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user