import { createHandler } from './_shared/middleware' import { adminDb } from './_shared/db' import { resolveTypeId, resolveAgentId, resolvePromptConfigId } from './_shared/resolve-ids' import { runAgent } from './_shared/index' import { create } from './admin-data' /** * @module custom_support-solution * @layer custom/cortex * @stability custom * * Solution AI — internal AI collaborator for support agents working a case. * Powered by core's runAgent() with the 'solution_ai' prompt config. * Never visible to the customer (all messages are visibility='internal'). * * Two actions: * POST ?action=start — ensures an internal AI thread exists for the ticket, * sends the first message, returns thread + agent message. * POST ?action=message — sends a follow-up message on the existing AI thread. * * Response: { threadId, agentMessageId, content } * * The AI thread is distinct from the regular internal thread: it is tagged with * data.thread_purpose = 'solution_ai' so the UI can find it deterministically. */ // ─── ACTION: START ───────────────────────────────────────────────────────────── async function handleStart(ctx: any, body: any) { const { ticket_id, message } = body if (!ticket_id) throw new Error('ticket_id is required') if (!message) throw new Error('message is required') const [threadTypeId, solutionAgentId, promptConfigId] = await Promise.all([ resolveTypeId('thread', 'support_thread'), resolveAgentId('Solution AI Agent'), resolvePromptConfigId('solution_ai'), ]) // Find existing solution AI thread for this ticket const { data: existing } = await adminDb .from('threads') .select('id') .eq('target_type', 'items') .eq('target_id', ticket_id) .eq('visibility', 'internal') .eq('data->>thread_purpose', 'solution_ai') .maybeSingle() let threadId = existing?.id if (!threadId) { const thread = await create(ctx, { entity: 'threads', type_id: threadTypeId, target_type: 'items', target_id: ticket_id, visibility: 'internal', status: 'active', data: { thread_purpose: 'solution_ai', agent_id: solutionAgentId, prompt_config_id: promptConfigId, } }) if (!thread?.id) throw new Error('Failed to create solution AI thread') threadId = thread.id } const agentMsg = await runAgent(threadId, message, ctx) return { threadId, agentMessageId: agentMsg?.id, content: agentMsg?.content || '', } } // ─── ACTION: MESSAGE ─────────────────────────────────────────────────────────── async function handleMessage(ctx: any, body: any) { const { thread_id, message } = body if (!thread_id) throw new Error('thread_id is required') if (!message) throw new Error('message is required') const agentMsg = await runAgent(thread_id, message, ctx) return { threadId: thread_id, agentMessageId: agentMsg?.id, content: agentMsg?.content || '', } } // ─── HANDLER ────────────────────────────────────────────────────────────────── export const handler = createHandler(async (ctx, body) => { const action = (ctx as any).query?.action switch (action) { case 'start': return handleStart(ctx, body) case 'message': return handleMessage(ctx, body) default: throw new Error(`Unknown action: ${action}. Use 'start' or 'message'.`) } })