import { MUTATION_TOOLS } from "./assistant-tools.js"; import type { PendingAssistantApproval } from "./assistant-approvals.js"; export const ASSISTANT_CONFIRMATION_PREFIX = "CONFIRMATION_REQUIRED:"; export type ChatMessage = { role: "user" | "assistant"; content: string }; export function parseToolArguments(args: string): Record { try { const parsed = JSON.parse(args) as unknown; return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record : {}; } catch { return {}; } } export function formatApprovalValue(value: unknown): string { if (typeof value === "string") { return value.length > 48 ? `${value.slice(0, 45)}...` : value; } if (typeof value === "number" || typeof value === "boolean") { return String(value); } if (Array.isArray(value)) { if (value.length === 0) return "[]"; return `[${value.slice(0, 3).map((item) => formatApprovalValue(item)).join(", ")}${value.length > 3 ? ", ..." : ""}]`; } if (value && typeof value === "object") { return "{...}"; } return "null"; } export function buildApprovalSummary(toolName: string, toolArguments: string): string { const params = parseToolArguments(toolArguments); const details = Object.entries(params) .filter(([, value]) => value !== undefined && value !== null && value !== "") .slice(0, 4) .map(([key, value]) => `${key}=${formatApprovalValue(value)}`) .join(", "); const action = toolName.replace(/_/g, " "); return details ? `${action} (${details})` : action; } export function isAffirmativeConfirmationReply(content: string): boolean { const normalized = content.trim().toLowerCase(); if (!normalized) return false; const exactMatches = new Set([ "ja", "yes", "y", "ok", "okay", "okey", "mach das", "bitte machen", "bitte ausführen", "bitte ausfuehren", "ausführen", "ausfuehren", "bestätigt", "bestaetigt", "bestätigen", "bestaetigen", "confirm", "confirmed", "do it", "go ahead", "proceed", ]); if (exactMatches.has(normalized)) return true; const affirmativePatterns = [ /^(ja|yes|ok|okay)\b/, /\b(mach|make|do|führ|fuehr|execute|run)\b.*\b(das|it|bitte|jetzt)\b/, /\b(bit(?:te)?|please)\b.*\b(ausführen|ausfuehren|execute|run|machen|do)\b/, /\b(bestätig|bestaetig|confirm)\w*\b/, /\b(go ahead|proceed)\b/, ]; return affirmativePatterns.some((pattern) => pattern.test(normalized)); } export function isCancellationReply(content: string): boolean { const normalized = content.trim().toLowerCase(); if (!normalized) return false; const exactMatches = new Set([ "nein", "no", "abbrechen", "cancel", "stopp", "stop", "doch nicht", "nicht ausführen", "nicht ausfuehren", ]); if (exactMatches.has(normalized)) return true; return [ /\b(nein|no|cancel|abbrechen|stop|stopp)\b/, /\b(doch nicht|nicht ausführen|nicht ausfuehren)\b/, ].some((pattern) => pattern.test(normalized)); } export function hasPendingAssistantConfirmation(messages: ChatMessage[]): boolean { if (messages.length < 2) return false; const lastMessage = messages[messages.length - 1]; if (!lastMessage || lastMessage.role !== "user") return false; for (let index = messages.length - 2; index >= 0; index -= 1) { const message = messages[index]; if (!message) continue; if (message.role === "assistant") { return message.content.trimStart().startsWith(ASSISTANT_CONFIRMATION_PREFIX); } } return false; } export function canExecuteMutationTool( messages: ChatMessage[], toolName: string, pendingApproval?: PendingAssistantApproval | null, ): boolean { if (!MUTATION_TOOLS.has(toolName)) return true; const lastMessage = messages[messages.length - 1]; if (!lastMessage || lastMessage.role !== "user") return false; if (!isAffirmativeConfirmationReply(lastMessage.content)) return false; if (pendingApproval) { return pendingApproval.toolName === toolName && pendingApproval.expiresAt > Date.now(); } return hasPendingAssistantConfirmation(messages); }