import { createAuditEntry } from "../lib/audit.js"; import { clearPendingAssistantApproval, consumePendingAssistantApproval, toApprovalPayload, type PendingAssistantApproval, } from "./assistant-approvals.js"; import { canExecuteMutationTool, isCancellationReply, parseToolArguments, type ChatMessage, } from "./assistant-confirmation.js"; import { buildAssistantInsight, type AssistantInsight } from "./assistant-insights.js"; import { readToolError, readToolSuccessMessage } from "./assistant-tool-results.js"; import { executeTool, type ToolAction, type ToolContext } from "./assistant-tools.js"; export type AssistantChatResponse = { content: string; role: "assistant"; approval?: ReturnType; insights?: AssistantInsight[]; actions?: ToolAction[]; }; export function mergeInsights(existing: AssistantInsight[], next: AssistantInsight): AssistantInsight[] { const duplicateIndex = existing.findIndex((item) => item.kind === next.kind && item.title === next.title && item.subtitle === next.subtitle); if (duplicateIndex >= 0) { const copy = [...existing]; copy[duplicateIndex] = next; return copy; } return [...existing, next].slice(-6); } export function buildAssistantChatResponse(input: { content: string; approval?: ReturnType | undefined; insights: AssistantInsight[]; actions: ToolAction[]; }): AssistantChatResponse { return { content: input.content, role: "assistant", ...(input.approval !== undefined ? { approval: input.approval } : {}), ...(input.insights.length > 0 ? { insights: input.insights } : {}), ...(input.actions.length > 0 ? { actions: input.actions } : {}), }; } export async function handlePendingAssistantApproval(input: { db: ToolContext["db"]; dbUserId?: string | undefined; toolCtx: ToolContext; conversationId: string; pendingApproval: PendingAssistantApproval | null; lastUserMessage?: ChatMessage | undefined; messages: ChatMessage[]; collectedActions: ToolAction[]; collectedInsights: AssistantInsight[]; }): Promise<{ response: AssistantChatResponse; collectedActions: ToolAction[]; collectedInsights: AssistantInsight[]; } | null> { const { pendingApproval, lastUserMessage } = input; if (!pendingApproval || lastUserMessage?.role !== "user") { return null; } if (isCancellationReply(lastUserMessage.content)) { await clearPendingAssistantApproval(input.db, input.toolCtx.userId, input.conversationId); void createAuditEntry({ db: input.db, entityType: "AiToolExecution", entityId: pendingApproval.id, entityName: pendingApproval.toolName, action: "DELETE", source: "ai", summary: `AI approval cancelled: ${pendingApproval.toolName}`, after: { approvalId: pendingApproval.id, params: parseToolArguments(pendingApproval.toolArguments), executed: false, }, ...(input.dbUserId !== undefined ? { userId: input.dbUserId } : {}), }); return { response: buildAssistantChatResponse({ content: `Aktion verworfen: ${pendingApproval.summary}`, approval: toApprovalPayload(pendingApproval, "cancelled"), insights: input.collectedInsights, actions: input.collectedActions, }), collectedActions: input.collectedActions, collectedInsights: input.collectedInsights, }; } if (!canExecuteMutationTool(input.messages, pendingApproval.toolName, pendingApproval)) { return null; } const approvedAction = await consumePendingAssistantApproval(input.db, input.toolCtx.userId, input.conversationId) ?? pendingApproval; const result = await executeTool( approvedAction.toolName, approvedAction.toolArguments, input.toolCtx, ); let collectedInsights = input.collectedInsights; const insight = buildAssistantInsight(approvedAction.toolName, result.data); if (insight) { collectedInsights = mergeInsights(collectedInsights, insight); } const collectedActions = result.action ? [...input.collectedActions, result.action] : input.collectedActions; const errorMessage = readToolError(result); const successMessage = readToolSuccessMessage(result); const finalContent = errorMessage ? `Die bestätigte Aktion konnte nicht ausgeführt werden: ${errorMessage}` : successMessage ? `Ausgeführt: ${successMessage}` : `Ausgeführt: ${approvedAction.summary}`; void createAuditEntry({ db: input.db, entityType: "AiToolExecution", entityId: approvedAction.id, entityName: approvedAction.toolName, action: "CREATE", source: "ai", summary: errorMessage ? `AI confirmed tool failed: ${approvedAction.toolName}` : `AI executed previously approved tool: ${approvedAction.toolName}`, after: { approvalId: approvedAction.id, params: parseToolArguments(approvedAction.toolArguments), executed: !errorMessage, }, ...(input.dbUserId !== undefined ? { userId: input.dbUserId } : {}), }); return { response: buildAssistantChatResponse({ content: finalContent, approval: toApprovalPayload(approvedAction, "approved"), insights: collectedInsights, actions: collectedActions, }), collectedActions, collectedInsights, }; }