package rules import ( "encoding/json" "fmt" "strings" ) const WorkflowVersion = 1 type RuleKind string const ( RuleKindRule RuleKind = "rule" RuleKindFunction RuleKind = "function" ) type TriggerType string const ( TriggerMessageReceived TriggerType = "message_received" TriggerLabelAdded TriggerType = "label_added" TriggerLabelRemoved TriggerType = "label_removed" TriggerDriveFileCreated TriggerType = "drive_file_created" TriggerDriveFileUpdated TriggerType = "drive_file_updated" TriggerDriveFileDeleted TriggerType = "drive_file_deleted" TriggerDriveFileMoved TriggerType = "drive_file_moved" TriggerDriveShareUpdated TriggerType = "drive_share_updated" TriggerContactCreated TriggerType = "contact_created" TriggerContactUpdated TriggerType = "contact_updated" TriggerContactDeleted TriggerType = "contact_deleted" TriggerCalendarEventCreated TriggerType = "calendar_event_created" TriggerCalendarEventUpdated TriggerType = "calendar_event_updated" TriggerCalendarEventDeleted TriggerType = "calendar_event_deleted" TriggerCalendarEventResponse TriggerType = "calendar_event_response" ) type Trigger struct { Type TriggerType `json:"type"` FolderID string `json:"folder_id,omitempty"` Label string `json:"label,omitempty"` AccountID string `json:"account_id,omitempty"` FolderPath string `json:"folder_path,omitempty"` ContactLabel string `json:"contact_label,omitempty"` CalendarID string `json:"calendar_id,omitempty"` } type TriggerGroup struct { Operator string `json:"operator"` // "or" Groups []TriggerAnd `json:"groups"` } type TriggerAnd struct { Operator string `json:"operator"` // "and" Items []Trigger `json:"items"` } type ExecVariable struct { Name string `json:"name"` Type string `json:"type"` // string, number, boolean Default string `json:"default,omitempty"` } type WorkflowNode struct { ID string `json:"id"` Type string `json:"type"` Position json.RawMessage `json:"position,omitempty"` Data json.RawMessage `json:"data"` } type WorkflowEdge struct { ID string `json:"id"` Source string `json:"source"` Target string `json:"target"` SourceHandle string `json:"sourceHandle,omitempty"` } type Workflow struct { Version int `json:"version"` Kind RuleKind `json:"kind"` Triggers TriggerGroup `json:"triggers"` Variables []ExecVariable `json:"variables,omitempty"` Nodes []WorkflowNode `json:"nodes"` Edges []WorkflowEdge `json:"edges"` } type ConditionNodeData struct { Field string `json:"field"` Operator string `json:"operator"` Value string `json:"value"` } type LabelCheckNodeData struct { Label string `json:"label"` Operator string `json:"operator"` // has, not_has } type SwitchCase struct { Value string `json:"value"` Label string `json:"label,omitempty"` } type SwitchNodeData struct { Field string `json:"field"` Cases []SwitchCase `json:"cases"` } type LLMCheckNodeData struct { Prompt string `json:"prompt"` Provider string `json:"provider,omitempty"` Model string `json:"model,omitempty"` } type ActionItem struct { Type string `json:"type"` Value string `json:"value"` } type ActionsNodeData struct { Actions []ActionItem `json:"actions"` } type SetVarNodeData struct { Name string `json:"name"` Value string `json:"value"` } type CallRuleNodeData struct { RuleID string `json:"rule_id"` } type EventContext struct { Type TriggerType FolderID string Label string FolderPath string ContactLabel string CalendarID string // Drive payload (when domain is drive) DriveFileName string DriveFilePath string DriveMimeType string DriveFileSize int64 DriveIsFolder bool // Contact payload (when domain is contacts) ContactID string ContactBookID string ContactName string ContactEmail string ContactPhone string ContactOrg string // Calendar payload (when domain is agenda) CalendarEventTitle string CalendarEventLocation string CalendarEventOrganizer string CalendarEventAttendee string CalendarEventStart string CalendarEventEnd string CalendarEventAllDay bool CalendarEventHasVideo bool CalendarEventUID string } func ParseWorkflow(raw []byte) (*Workflow, error) { if len(raw) == 0 || string(raw) == "null" { return nil, nil } var wf Workflow if err := json.Unmarshal(raw, &wf); err != nil { return nil, fmt.Errorf("parse workflow: %w", err) } if wf.Version == 0 { wf.Version = WorkflowVersion } if wf.Kind == "" { wf.Kind = RuleKindRule } return &wf, nil } func (wf *Workflow) nodeMap() map[string]WorkflowNode { m := make(map[string]WorkflowNode, len(wf.Nodes)) for _, n := range wf.Nodes { m[n.ID] = n } return m } func (wf *Workflow) outgoingEdges(nodeID string) []WorkflowEdge { var out []WorkflowEdge for _, e := range wf.Edges { if e.Source == nodeID { out = append(out, e) } } return out } func (wf *Workflow) nextNode(nodeID, handle string) string { for _, e := range wf.Edges { if e.Source == nodeID && e.SourceHandle == handle { return e.Target } } return "" } func (wf *Workflow) nextDefault(nodeID string) string { for _, e := range wf.Edges { if e.Source == nodeID && e.SourceHandle == "" { return e.Target } } return "" } func (wf *Workflow) findStartNode() string { for _, n := range wf.Nodes { if n.Type == "start" { return n.ID } } return "" } func matchesTriggers(triggers TriggerGroup, msg *Message, evt *EventContext) bool { if len(triggers.Groups) == 0 { return true } for _, group := range triggers.Groups { if matchesTriggerAnd(group, msg, evt) { return true } } return false } func matchesTriggerAnd(group TriggerAnd, msg *Message, evt *EventContext) bool { if len(group.Items) == 0 { return true } for _, t := range group.Items { if !matchTrigger(t, msg, evt) { return false } } return true } func matchTrigger(t Trigger, msg *Message, evt *EventContext) bool { switch t.Type { case TriggerMessageReceived: if evt != nil && evt.Type != TriggerMessageReceived && evt.Type != "" { return false } if t.AccountID != "" && msg.AccountID != "" && t.AccountID != msg.AccountID { return false } if t.FolderID != "" && msg.FolderID != "" && t.FolderID != msg.FolderID { return false } return true case TriggerLabelAdded: if evt == nil || evt.Type != TriggerLabelAdded { return false } if t.Label != "" && t.Label != evt.Label { return false } return true case TriggerLabelRemoved: if evt == nil || evt.Type != TriggerLabelRemoved { return false } if t.Label != "" && t.Label != evt.Label { return false } return true case TriggerDriveFileCreated, TriggerDriveFileUpdated, TriggerDriveFileDeleted, TriggerDriveFileMoved, TriggerDriveShareUpdated: if evt == nil || evt.Type != t.Type { return false } if t.FolderPath != "" && evt.FolderPath != "" && !strings.HasPrefix(evt.DriveFilePath, t.FolderPath) { return false } return true case TriggerContactCreated, TriggerContactUpdated, TriggerContactDeleted: if evt == nil || evt.Type != t.Type { return false } if t.ContactLabel != "" && evt.ContactLabel != "" && t.ContactLabel != evt.ContactLabel { return false } return true case TriggerCalendarEventCreated, TriggerCalendarEventUpdated, TriggerCalendarEventDeleted, TriggerCalendarEventResponse: if evt == nil || evt.Type != t.Type { return false } if t.CalendarID != "" && evt.CalendarID != "" && t.CalendarID != evt.CalendarID { return false } return true default: return false } }