/** * Agent Trigger Types — Public API * * Shared type definitions for agent triggers between @notionhq/workers and the Notion repository. * This file MUST be kept identical in both repositories. * * Conventions: * - Protocols define the standard fields every consumer can rely on. * - Vendor-specific fields use `x--` prefix (e.g., "x-slack-threadId"). * - Only public URLs — no internal URIs. * - No `data` sub-object — vendor fields live at the top level with `x--`. * - Types only — no runtime values. * - All fields are required. Nullable fields use `T | null` (no `?:`) so they * map directly to required + nullable in JSON Schema for LLM structured output. * - Conversions must always provide explicit values (use `null` when absent). */ // ============================================================ // Vendor extension pattern // ============================================================ /** Allows arbitrary vendor-specific fields prefixed with `x--`. */ export type VendorExtensions = { [key: `x-${string}`]: unknown; }; // ============================================================ // Protocols (composable capabilities) // ============================================================ /** Has a human-readable message / summary. */ export type MessageLike = { message: string; }; /** Has an actor who performed the action. */ export type ActorLike = { actor: Actor | null; }; /** Has a navigable URL. */ export type LinkLike = { url: string | null; }; /** Has a timestamp. */ export type TimestampLike = { /** ISO 8601 */ timestamp: string | null; }; /** Has a conversation thread. */ export type ThreadLike = { thread: Array; /** Whether this event is a follow-up in a previously subscribed thread. */ isFollowUpMessage: boolean | null; }; /** * A chat message. Any chat platform (Slack, Discord, MS Teams, etc.) implements this. * Composes MessageLike, ActorLike, LinkLike, TimestampLike. */ export type ChatMessageLike = MessageLike & ActorLike & LinkLike & TimestampLike & { id: string; channelId: string; channelName: string | null; }; /** An email message. Any mail provider (Gmail, Outlook, etc.) implements this. */ export type EmailMessageLike = MessageLike & LinkLike & TimestampLike & { id: string; subject: string; from: string; fromName: string | null; to: Array; cc: Array | null; bcc: Array | null; /** Plain-text or HTML body content. */ body: string; isRead: boolean | null; hasAttachments: boolean | null; labels: Array | null; }; /** An email thread. Any mail provider implements this. */ export type EmailThreadLike = { id: string; subject: string; participants: Array; messageCount: number; /** ISO 8601 */ lastMessageAt: string; hasUnread: boolean; }; /** A calendar event. Any calendar provider (Google, Outlook, iCal, etc.) implements this. */ export type CalendarEventLike = LinkLike & TimestampLike & { eventId: string; summary: string; description: string | null; location: string | null; /** ISO 8601 */ startTime: string; /** ISO 8601 */ endTime: string | null; isAllDay: boolean | null; isRecurring: boolean | null; attendees: Array | null; calendarId: string; accountId: string; }; // ============================================================ // Core types // ============================================================ /** * An actor who performed an action. * * `id` / `displayName` / `email` are the Notion-resolved identity. * `vendorActor` carries the identity in the originating system. */ export type Actor = { /** Notion user ID (when resolved). */ id: string | null; displayName: string | null; email: string | null; /** The actor's identity in the vendor system. */ vendorActor: VendorActor | null; }; /** Vendor-specific actor identity. */ export type VendorActor = { /** Vendor name: "slack", "gmail", "discord", "notion", etc. */ vendor: string; /** Vendor-specific ID. */ id: string; displayName: string | null; /** Handle / username (e.g., Slack @handle). */ handle: string | null; /** "user", "bot", or a vendor-specific string. */ type: "user" | "bot" | string | null; /** URL to the actor's profile in the vendor system. */ url: string | null; }; // ============================================================ // Thread message // ============================================================ /** * A single message inside a thread. * Implements MessageLike, ActorLike, LinkLike, TimestampLike. * Vendor-specific fields use `x--` prefix. */ export type ThreadMessage = MessageLike & ActorLike & LinkLike & TimestampLike & VendorExtensions & { id: string; }; /** A chat thread message. Extends ChatMessageLike with VendorExtensions. */ export type ChatThreadMessage = ChatMessageLike & VendorExtensions; /** An email message in a trigger. Extends EmailMessageLike with VendorExtensions. */ export type EmailTriggerMessage = EmailMessageLike & VendorExtensions; // ============================================================ // Trigger event base // ============================================================ /** * Every trigger event has at least these fields. * `message` is a human-readable summary of what happened. */ export type TriggerEventBase = MessageLike & ActorLike & VendorExtensions & { type: string; vendor: string; }; /** * Base shape for chat trigger events (Slack, Discord, MS Teams, etc.). * Parameterized on the message type so each platform can narrow * `triggerMessage` and `thread` to its vendor-specific message shape. */ type ChatEventBase = TriggerEventBase & LinkLike & TimestampLike & ThreadLike & { /** The message that fired the trigger. */ triggerMessage: M; channelId: string; channelName: string | null; threadId: string | null; }; // ============================================================ // Slack // ============================================================ export type SlackChatThreadMessage = ChatThreadMessage & { "x-slack-fileUrls": Array | null; /** URLs extracted from the message text. */ "x-slack-urls": Array | null; }; export type SlackMessageEvent = ChatEventBase & { type: "slack.message"; vendor: "slack"; }; export type SlackReactionAddedEvent = ChatEventBase & { type: "slack.reaction.added"; vendor: "slack"; /** The emoji name (without colons), e.g. "thumbsup". */ "x-slack-reaction": string | null; }; export type SlackAppMentionEvent = ChatEventBase & { type: "slack.app.mention"; vendor: "slack"; }; // ============================================================ // Mail // ============================================================ /** Base shape for all mail trigger events. */ type MailEventBase = TriggerEventBase & LinkLike & TimestampLike & { vendor: "notionmail" | "gmail" | string; /** The triggering email. */ email: EmailTriggerMessage; /** The email thread this message belongs to. */ emailThread: EmailThreadLike; /** The email address of the mailbox owner. */ userEmail: string; }; export type MailEmailReceivedEvent = MailEventBase & { type: "mail.email.received"; }; export type MailEmailSentEvent = MailEventBase & { type: "mail.email.sent"; }; export type MailLabelAppliedEvent = MailEventBase & { type: "mail.label.applied"; /** The label that was applied. */ appliedLabel: string; }; // ============================================================ // Calendar // ============================================================ /** Base shape for all calendar trigger events. */ type CalendarEventBase = TriggerEventBase & CalendarEventLike & { vendor: "notion-calendar" | "google" | string; }; export type CalendarEventCreatedEvent = CalendarEventBase & { type: "calendar.event.created"; }; export type CalendarEventUpdatedEvent = CalendarEventBase & { type: "calendar.event.updated"; /** Which fields changed. */ updatedFields: Array | null; }; export type CalendarEventCanceledEvent = CalendarEventBase & { type: "calendar.event.canceled"; }; // ============================================================ // Discord // ============================================================ export type DiscordChatThreadMessage = ChatThreadMessage & { "x-discord-guildId": string | null; }; export type DiscordInteractionEvent = ChatEventBase & { type: "discord.interaction"; vendor: "discord"; "x-discord-guildId": string | null; }; // ============================================================ // Notion — Page / Database triggers // ============================================================ /** A property edit with before/after snapshots. */ export type NotionPageEdit = { before: Record | null; after: Record | null; afterContent: string | null; contentChanged: boolean | null; timestamp: number; editedBy: Actor | null; }; export type NotionPageCreatedEvent = TriggerEventBase & LinkLike & TimestampLike & { type: "notion.page.created"; vendor: "notion"; /** Page properties as key-value pairs. */ page: Record; /** Rendered markdown content of the page. */ content: string; edits: Array | null; }; export type NotionPageUpdatedEvent = TriggerEventBase & LinkLike & TimestampLike & { type: "notion.page.updated"; vendor: "notion"; /** Chronological array of edits (accumulated during debounce). */ edits: Array; }; export type NotionPageDeletedEvent = TriggerEventBase & LinkLike & TimestampLike & { type: "notion.page.deleted"; vendor: "notion"; page: Record; content: string; }; export type NotionMeetingNoteSummaryCompletedEvent = TriggerEventBase & LinkLike & TimestampLike & { type: "notion.meetingNote.summary.completed"; vendor: "notion"; page: Record; content: string; meetingNoteBlockId: string; }; // ============================================================ // Notion — Discussion / Comment triggers // ============================================================ export type NotionCommentAddedEvent = TriggerEventBase & LinkLike & TimestampLike & { type: "notion.page.discussion.comment.added"; vendor: "notion"; /** The full discussion context. */ discussion: { id: string; pageId: string; resolved: boolean; context: "inline" | "block" | "page" | "databaseProperty" | null; parentBlockId: string | null; propertyId: string | null; propertyName: string | null; }; /** The new comment. */ comment: { id: string; text: string; url: string; timestamp: string; actor: Actor | null; }; }; // ============================================================ // Notion — Agent / Button triggers // ============================================================ export type NotionAgentMentionedEvent = TriggerEventBase & LinkLike & TimestampLike & { type: "notion.agent.mentioned"; vendor: "notion"; page: Record; content: string; mentionLocation: "page_content" | "person_property"; mentionBlockContent: string | null; propertyId: string | null; propertyName: string | null; }; export type NotionButtonPressedEvent = TriggerEventBase & LinkLike & TimestampLike & { type: "notion.button.pressed"; vendor: "notion"; blockUrl: string; }; export type NotionDatabaseAgentUpdatedEvent = TriggerEventBase & TimestampLike & { type: "notion.database.agent.updated"; vendor: "notion"; agentId: string; runtimeInstructions: string; shouldRunOverDatabase: boolean; viewId: string | null; }; export type WebhookEvent = TriggerEventBase & TimestampLike & { type: "webhooks.webhook"; vendor: "webhooks"; triggerId: string; verificationSchema: string; eventType: string | null; deliveryId: string | null; payload: unknown; }; // ============================================================ // Google Drive OAuth — File changed (polling) trigger // ============================================================ export type GoogleDriveOauthFilesChangedEvent = TriggerEventBase & TimestampLike & { type: "googleDriveOauth.filesChanged"; vendor: "google-drive"; /** The page token from before this poll. */ changeToken: string; /** The page token after this poll. */ newChangeToken: string; /** Number of changes detected. */ changeCount: number; /** The folder or drive being watched. */ scope: { type: "folder"; folderId: string } | { type: "drive"; driveId: string }; /** Upper time bound for this poll window (ISO 8601). */ lastEventTimestamp: string; }; // ============================================================ // Recurrence (scheduled) trigger // ============================================================ export type RecurrenceFrequency = "hour" | "day" | "week" | "month" | "year"; export type RecurrenceEvent = TriggerEventBase & TimestampLike & { type: "recurrence"; vendor: "notion"; frequency: RecurrenceFrequency; interval: number; timezone: string; }; // ============================================================ // Derived union types // ============================================================ /** Map from trigger type string to its event shape. Single source of truth. */ export type TriggerEventMap = { "slack.message": SlackMessageEvent; "slack.reaction.added": SlackReactionAddedEvent; "slack.app.mention": SlackAppMentionEvent; "discord.interaction": DiscordInteractionEvent; "mail.email.received": MailEmailReceivedEvent; "mail.email.sent": MailEmailSentEvent; "mail.label.applied": MailLabelAppliedEvent; "calendar.event.created": CalendarEventCreatedEvent; "calendar.event.updated": CalendarEventUpdatedEvent; "calendar.event.canceled": CalendarEventCanceledEvent; "notion.page.created": NotionPageCreatedEvent; "notion.page.updated": NotionPageUpdatedEvent; "notion.page.deleted": NotionPageDeletedEvent; "notion.meetingNote.summary.completed": NotionMeetingNoteSummaryCompletedEvent; "notion.page.discussion.comment.added": NotionCommentAddedEvent; "notion.agent.mentioned": NotionAgentMentionedEvent; "notion.button.pressed": NotionButtonPressedEvent; "notion.database.agent.updated": NotionDatabaseAgentUpdatedEvent; "webhooks.webhook": WebhookEvent; "googleDriveOauth.filesChanged": GoogleDriveOauthFilesChangedEvent; recurrence: RecurrenceEvent; }; /** All trigger type strings. Derived from TriggerEventMap. */ export type TriggerType = keyof TriggerEventMap; /** Discriminated union of all trigger events. Derived from TriggerEventMap. */ export type TriggerEvent = TriggerEventMap[TriggerType];