import { readBody } from "h3"; import { createUIMessageStream, createUIMessageStreamResponse } from "ai"; import { getAuthContext } from "../utils/auth"; import { prisma } from "../utils/prisma"; import { buildChangeSet, captureSnapshot } from "../utils/changeSet"; import { persistChatMessage, runCrmAgentFor, type AgentTraceEvent } from "../agent/crmAgent"; import type { ChangeSet } from "../utils/changeSet"; function extractMessageText(message: any): string { if (!message || !Array.isArray(message.parts)) return ""; return message.parts .filter((part: any) => part?.type === "text" && typeof part.text === "string") .map((part: any) => part.text) .join("") .trim(); } function getLastUserText(messages: any[]): string { for (let i = messages.length - 1; i >= 0; i -= 1) { const message = messages[i]; if (message?.role !== "user") continue; const text = extractMessageText(message); if (text) return text; } return ""; } function humanizeTraceText(trace: AgentTraceEvent): string { if (trace.toolRun?.name) { return `Использую инструмент: ${trace.toolRun.name}`; } const text = (trace.text ?? "").trim(); if (!text) return "Агент работает с данными CRM."; if (text.toLowerCase().includes("ошиб")) return "Возникла ошибка шага, пробую другой путь."; if (text.toLowerCase().includes("итог")) return "Готовлю финальный ответ."; return text; } function renderChangeSetSummary(changeSet: ChangeSet): string { const totals = { created: 0, updated: 0, deleted: 0 }; for (const item of changeSet.items) { if (item.action === "created") totals.created += 1; else if (item.action === "updated") totals.updated += 1; else if (item.action === "deleted") totals.deleted += 1; } const byEntity = new Map(); for (const item of changeSet.items) { byEntity.set(item.entity, (byEntity.get(item.entity) ?? 0) + 1); } const lines = [ "Technical change summary", `Total: ${changeSet.items.length} · Created: ${totals.created} · Updated: ${totals.updated} · Archived: ${totals.deleted}`, ...[...byEntity.entries()].map(([entity, count]) => `- ${entity}: ${count}`), ]; return lines.join("\n"); } export default defineEventHandler(async (event) => { const auth = await getAuthContext(event); const body = await readBody<{ messages?: any[] }>(event); const messages = Array.isArray(body?.messages) ? body.messages : []; const userText = getLastUserText(messages); if (!userText) { throw createError({ statusCode: 400, statusMessage: "Last user message is required" }); } const requestId = `req_${Date.now()}_${Math.floor(Math.random() * 1_000_000)}`; const stream = createUIMessageStream({ execute: async ({ writer }) => { const textId = `text-${Date.now()}`; writer.write({ type: "start" }); try { const snapshotBefore = await captureSnapshot(prisma, auth.teamId); await persistChatMessage({ teamId: auth.teamId, conversationId: auth.conversationId, authorUserId: auth.userId, role: "USER", text: userText, requestId, eventType: "user", phase: "final", transient: false, }); const reply = await runCrmAgentFor({ teamId: auth.teamId, userId: auth.userId, userText, requestId, conversationId: auth.conversationId, onTrace: async (trace: AgentTraceEvent) => { writer.write({ type: "data-agent-log", data: { requestId, at: new Date().toISOString(), text: humanizeTraceText(trace), }, }); }, }); const snapshotAfter = await captureSnapshot(prisma, auth.teamId); const changeSet = buildChangeSet(snapshotBefore, snapshotAfter); await persistChatMessage({ teamId: auth.teamId, conversationId: auth.conversationId, authorUserId: null, role: "ASSISTANT", text: reply.text, requestId, eventType: "assistant", phase: "final", transient: false, }); if (changeSet) { await persistChatMessage({ teamId: auth.teamId, conversationId: auth.conversationId, authorUserId: null, role: "ASSISTANT", text: renderChangeSetSummary(changeSet), requestId, eventType: "note", phase: "final", transient: false, messageKind: "change_set_summary", changeSet, }); } writer.write({ type: "text-start", id: textId }); writer.write({ type: "text-delta", id: textId, delta: reply.text }); writer.write({ type: "text-end", id: textId }); writer.write({ type: "finish", finishReason: "stop" }); } catch (error: any) { const errorText = String(error?.message ?? error); await persistChatMessage({ teamId: auth.teamId, conversationId: auth.conversationId, authorUserId: null, role: "ASSISTANT", text: errorText, requestId, eventType: "assistant", phase: "error", transient: false, }); writer.write({ type: "data-agent-log", data: { requestId, at: new Date().toISOString(), text: "Ошибка выполнения агентского цикла.", }, }); writer.write({ type: "text-start", id: textId }); writer.write({ type: "text-delta", id: textId, delta: errorText, }); writer.write({ type: "text-end", id: textId }); writer.write({ type: "finish", finishReason: "stop" }); } }, }); return createUIMessageStreamResponse({ stream }); });