import { getStringArg } from './toolWidgetUtils'; import type { ToolWidgetStatus } from './toolWidgetUtils'; /** * Pure logic for the automation tool widgets (list_automations, * create_automation, manage_automation) — the superagent subset of the web * builder's AutomationToolUI factory + CreateAutomationWidget * (frontend/apps/builder/.../tools-ui/Components/AutomationToolUI.tsx, * CreateAutomationWidget.tsx, utils/automationUtils.ts). */ export type AutomationAction = | 'create' | 'update' | 'delete' | 'toggle' | 'list' | 'archive' | 'unarchive'; export type AutomationType = 'scheduled' | 'entity' | 'connector'; const TOOL_TO_ACTION: Record = { create_automation: 'create', list_automations: 'list', manage_automation: 'update', }; // English values of the web's tools. translation keys. const ACTION_VERBS: Record = { create: { running: 'Creating', completed: 'Created' }, update: { running: 'Updating', completed: 'Updated' }, delete: { running: 'Deleting', completed: 'Deleted' }, toggle: { running: 'Toggling', completed: 'Toggled' }, list: { running: 'Listing', completed: 'Listed' }, archive: { running: 'Archiving', completed: 'Archived' }, unarchive: { running: 'Unarchiving', completed: 'Unarchived' }, }; /** The web reads the action from args first (manage_automation), then the tool name. */ export function getAutomationAction( toolName: string, args: Record | null, ): AutomationAction { const argAction = getStringArg(args, ['action']); // Own-key check: `in` would also match Object.prototype keys ("toString", …). if (Object.prototype.hasOwnProperty.call(ACTION_VERBS, argAction)) { return argAction as AutomationAction; } return TOOL_TO_ACTION[toolName] ?? 'update'; } export function getAutomationType(args: Record | null): AutomationType | null { const type = getStringArg(args, ['automation_type']); return type === 'scheduled' || type === 'entity' || type === 'connector' ? type : null; } function getAutomationLabel( toolName: string, args: Record | null, plural: boolean, ): string { // list_automations spans every type, so it always uses the generic label. const type = toolName === 'list_automations' ? null : getAutomationType(args); const base = type === 'scheduled' ? 'scheduled automation' : type === 'entity' ? 'data automation' : 'automation'; return plural ? `${base}s` : base; } // Web AutomationToolUIFactory defaultNewName — a literal "automation" name is // the model echoing the placeholder, not a real name, so no chip. const DEFAULT_NEW_NAME = 'automation'; function getAutomationDisplayName( action: AutomationAction, args: Record | null, ): string | null { if (action === 'list') return null; const name = getStringArg(args, ['name']); if (action === 'create') return name && name !== DEFAULT_NEW_NAME ? name : null; const fallback = getStringArg(args, ['automation_name']) || getStringArg(args, ['automation_id']).slice(0, 8); return (action === 'update' ? name || fallback : fallback) || null; } export type AutomationRowInfo = { title: string; chipText: string | null; type: AutomationType | null; }; /** Header line + name chip for the slim automation row, mirroring the web verb/label/chip. */ export function getAutomationRowInfo( toolName: string, args: Record | null, status: ToolWidgetStatus, ): AutomationRowInfo { const action = getAutomationAction(toolName, args); const label = getAutomationLabel(toolName, args, action === 'list'); // Stopped/rejected must not read as completed (same as the create widgets). const verb = status === 'running' ? ACTION_VERBS[action].running : status === 'error' || status === 'stopped' ? `Failed to ${action}` : ACTION_VERBS[action].completed; return { title: `${verb} ${label}`, chipText: getAutomationDisplayName(action, args), // Web automationIconSelector: list_automations always gets the generic // icon, even when the call filters by automation_type. type: toolName === 'list_automations' ? null : getAutomationType(args), }; } // ── create_automation trigger summary ─────────────────────────────────────── // Port of the web describeTrigger (tools-ui/utils/automationUtils.ts). const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; function joinEvents(value: unknown): string { return Array.isArray(value) ? value.map(String).join(', ') : ''; } export function describeTrigger(args: Record | null): string | null { if (!args) return null; const type = getAutomationType(args); if (type === 'scheduled') { const oneTimeDate = getStringArg(args, ['one_time_date']); if (args.schedule_mode === 'one-time' && oneTimeDate) { return `One-time: ${new Date(oneTimeDate).toLocaleString()}`; } const cron = getStringArg(args, ['cron_expression']); if (args.schedule_type === 'cron' && cron) { return `Cron: ${cron}`; } const parts: string[] = []; const interval = Number(args.repeat_interval); const unit = getStringArg(args, ['repeat_unit']); if (interval && unit) { parts.push(interval === 1 ? `Every ${unit.replace(/s$/, '')}` : `Every ${interval} ${unit}`); } if (unit === 'weeks' && Array.isArray(args.repeat_on_days) && args.repeat_on_days.length > 0) { const days = args.repeat_on_days.map((d) => DAY_NAMES[Number(d)] ?? `Day ${d}`); parts.push(`on ${days.join(', ')}`); } if (unit === 'months' && args.repeat_on_day_of_month) { parts.push(`on day ${args.repeat_on_day_of_month}`); } const startTime = getStringArg(args, ['start_time']); if (startTime) parts.push(`at ${startTime}`); return parts.length > 0 ? parts.join(' ') : null; } if (type === 'entity') { const entityName = getStringArg(args, ['entity_name']); if (!entityName) return null; const events = joinEvents(args.event_types); return events ? `${entityName} — ${events}` : entityName; } if (type === 'connector') { const integration = getStringArg(args, ['integration_type']); if (!integration) return null; const events = joinEvents(args.events); return events ? `${integration} — ${events}` : integration; } return null; }