diff --git a/apps/docs/content/docs/en/tools/gmail.mdx b/apps/docs/content/docs/en/tools/gmail.mdx index 261699d8875..5aceafc2f6e 100644 --- a/apps/docs/content/docs/en/tools/gmail.mdx +++ b/apps/docs/content/docs/en/tools/gmail.mdx @@ -90,6 +90,34 @@ Draft emails using Gmail. Returns API-aligned fields only. | `threadId` | string | Gmail thread ID | | `labelIds` | array | Email labels | +### `gmail_edit_draft` + +Update an existing Gmail draft in place without deleting and recreating it. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `draftId` | string | Yes | ID of the draft to update \(from Gmail List Drafts or Gmail Get Draft\) | +| `to` | string | Yes | Recipient email address | +| `subject` | string | No | Email subject | +| `body` | string | Yes | Email body content | +| `contentType` | string | No | Content type for the email body \(text or html\) | +| `threadId` | string | No | Thread ID to associate the draft with \(for threading\) | +| `replyToMessageId` | string | No | Gmail message ID to reply to - use the "id" field from Gmail Read results \(not the RFC "messageId"\) | +| `cc` | string | No | CC recipients \(comma-separated\) | +| `bcc` | string | No | BCC recipients \(comma-separated\) | +| `attachments` | file[] | No | Files to attach to the email draft | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `draftId` | string | Draft ID | +| `messageId` | string | Gmail message ID for the draft | +| `threadId` | string | Gmail thread ID | +| `labelIds` | array | Email labels | + ### `gmail_read` Read emails from Gmail. Returns API-aligned fields only. diff --git a/apps/docs/content/docs/en/tools/outlook.mdx b/apps/docs/content/docs/en/tools/outlook.mdx index 67123f889c9..aa31a8ff5e9 100644 --- a/apps/docs/content/docs/en/tools/outlook.mdx +++ b/apps/docs/content/docs/en/tools/outlook.mdx @@ -56,7 +56,6 @@ Send emails using Outlook | `body` | string | Yes | Email body content | | `contentType` | string | No | Content type for the email body \(text or html\) | | `replyToMessageId` | string | No | Message ID to reply to \(for threading\) | -| `conversationId` | string | No | Conversation ID for threading | | `cc` | string | No | CC recipients \(comma-separated\) | | `bcc` | string | No | BCC recipients \(comma-separated\) | | `attachments` | file[] | No | Files to attach to the email | diff --git a/apps/docs/content/docs/en/triggers/linear.mdx b/apps/docs/content/docs/en/triggers/linear.mdx index 6d9e41d807e..fa64c12e4fd 100644 --- a/apps/docs/content/docs/en/triggers/linear.mdx +++ b/apps/docs/content/docs/en/triggers/linear.mdx @@ -25,33 +25,6 @@ Trigger workflow when a new comment is created in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(Comment\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Comment ID | -| ↳ `body` | string | Comment body text | -| ↳ `edited` | boolean | Whether the comment body has been edited \(Linear webhook payload field\) | -| ↳ `url` | string | Comment URL | -| ↳ `issueId` | string | Issue ID this comment belongs to | -| ↳ `userId` | string | User ID of the comment author | -| ↳ `editedAt` | string | Last edited timestamp | -| ↳ `createdAt` | string | Comment creation timestamp | -| ↳ `updatedAt` | string | Comment last update timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `resolvedAt` | string | Resolved timestamp \(for comment threads\) | -| ↳ `parent` | object | Parent comment object \(if this is a reply\) | -| ↳ `reactionData` | object | Reaction data for the comment | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -66,33 +39,6 @@ Trigger workflow when a comment is updated in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(Comment\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Comment ID | -| ↳ `body` | string | Comment body text | -| ↳ `edited` | boolean | Whether the comment body has been edited \(Linear webhook payload field\) | -| ↳ `url` | string | Comment URL | -| ↳ `issueId` | string | Issue ID this comment belongs to | -| ↳ `userId` | string | User ID of the comment author | -| ↳ `editedAt` | string | Last edited timestamp | -| ↳ `createdAt` | string | Comment creation timestamp | -| ↳ `updatedAt` | string | Comment last update timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `resolvedAt` | string | Resolved timestamp \(for comment threads\) | -| ↳ `parent` | object | Parent comment object \(if this is a reply\) | -| ↳ `reactionData` | object | Reaction data for the comment | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -107,31 +53,6 @@ Trigger workflow when a new customer request is created in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(CustomerNeed\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Customer request ID | -| ↳ `body` | string | Request body content \(Markdown\) | -| ↳ `priority` | number | Request priority \(0 = Not important, 1 = Important\) | -| ↳ `customerId` | string | Customer ID | -| ↳ `issueId` | string | Linked issue ID | -| ↳ `projectId` | string | Associated project ID | -| ↳ `creatorId` | string | Creator user ID | -| ↳ `url` | string | Customer request URL | -| ↳ `createdAt` | string | Request creation timestamp | -| ↳ `updatedAt` | string | Request last update timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -146,31 +67,6 @@ Trigger workflow when a customer request is updated in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(CustomerNeed\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Customer request ID | -| ↳ `body` | string | Request body content \(Markdown\) | -| ↳ `priority` | number | Request priority \(0 = Not important, 1 = Important\) | -| ↳ `customerId` | string | Customer ID | -| ↳ `issueId` | string | Linked issue ID | -| ↳ `projectId` | string | Associated project ID | -| ↳ `creatorId` | string | Creator user ID | -| ↳ `url` | string | Customer request URL | -| ↳ `createdAt` | string | Request creation timestamp | -| ↳ `updatedAt` | string | Request last update timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -185,36 +81,6 @@ Trigger workflow when a new cycle is created in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(Cycle\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Cycle ID | -| ↳ `number` | number | Cycle number | -| ↳ `name` | string | Cycle name | -| ↳ `description` | string | Cycle description | -| ↳ `teamId` | string | Team ID | -| ↳ `startsAt` | string | Cycle start date | -| ↳ `endsAt` | string | Cycle end date | -| ↳ `completedAt` | string | Completed timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `autoArchivedAt` | string | Auto-archived timestamp | -| ↳ `createdAt` | string | Cycle creation timestamp | -| ↳ `updatedAt` | string | Cycle last update timestamp | -| ↳ `progress` | number | Cycle progress \(0-1\) | -| ↳ `scopeHistory` | array | History of scope changes | -| ↳ `completedScopeHistory` | array | History of completed scope | -| ↳ `inProgressScopeHistory` | array | History of in-progress scope | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -229,36 +95,6 @@ Trigger workflow when a cycle is updated in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(Cycle\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Cycle ID | -| ↳ `number` | number | Cycle number | -| ↳ `name` | string | Cycle name | -| ↳ `description` | string | Cycle description | -| ↳ `teamId` | string | Team ID | -| ↳ `startsAt` | string | Cycle start date | -| ↳ `endsAt` | string | Cycle end date | -| ↳ `completedAt` | string | Completed timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `autoArchivedAt` | string | Auto-archived timestamp | -| ↳ `createdAt` | string | Cycle creation timestamp | -| ↳ `updatedAt` | string | Cycle last update timestamp | -| ↳ `progress` | number | Cycle progress \(0-1\) | -| ↳ `scopeHistory` | array | History of scope changes | -| ↳ `completedScopeHistory` | array | History of completed scope | -| ↳ `inProgressScopeHistory` | array | History of in-progress scope | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -273,55 +109,6 @@ Trigger workflow when a new issue is created in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(Issue\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Issue ID | -| ↳ `title` | string | Issue title | -| ↳ `description` | string | Issue description | -| ↳ `identifier` | string | Issue identifier \(e.g., ENG-123\) | -| ↳ `number` | number | Issue number | -| ↳ `priority` | number | Issue priority \(0 = None, 1 = Urgent, 2 = High, 3 = Medium, 4 = Low\) | -| ↳ `estimate` | number | Issue estimate | -| ↳ `sortOrder` | number | Issue sort order | -| ↳ `teamId` | string | Team ID | -| ↳ `stateId` | string | Workflow state ID | -| ↳ `assigneeId` | string | Assignee user ID | -| ↳ `creatorId` | string | Creator user ID | -| ↳ `projectId` | string | Project ID | -| ↳ `cycleId` | string | Cycle ID | -| ↳ `parentId` | string | Parent issue ID \(for sub-issues\) | -| ↳ `labelIds` | array | Array of label IDs | -| ↳ `subscriberIds` | array | Array of subscriber user IDs | -| ↳ `url` | string | Issue URL | -| ↳ `branchName` | string | Git branch name | -| ↳ `customerTicketCount` | number | Number of customer tickets | -| ↳ `dueDate` | string | Issue due date | -| ↳ `snoozedUntilAt` | string | Snoozed until timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `canceledAt` | string | Canceled timestamp | -| ↳ `completedAt` | string | Completed timestamp | -| ↳ `startedAt` | string | Started timestamp | -| ↳ `triagedAt` | string | Triaged timestamp | -| ↳ `createdAt` | string | Issue creation timestamp | -| ↳ `updatedAt` | string | Issue last update timestamp | -| ↳ `autoArchivedAt` | string | Auto-archived timestamp | -| ↳ `autoClosedAt` | string | Auto-closed timestamp | -| ↳ `previousIdentifiers` | array | Array of previous issue identifiers \(when an issue is moved between teams\) | -| ↳ `integrationSourceType` | string | Integration source type \(if created from an integration\) | -| ↳ `slaStartedAt` | string | SLA timer started timestamp | -| ↳ `slaBreachesAt` | string | SLA breach timestamp | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -336,55 +123,6 @@ Trigger workflow when an issue is removed/deleted in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(Issue\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Issue ID | -| ↳ `title` | string | Issue title | -| ↳ `description` | string | Issue description | -| ↳ `identifier` | string | Issue identifier \(e.g., ENG-123\) | -| ↳ `number` | number | Issue number | -| ↳ `priority` | number | Issue priority \(0 = None, 1 = Urgent, 2 = High, 3 = Medium, 4 = Low\) | -| ↳ `estimate` | number | Issue estimate | -| ↳ `sortOrder` | number | Issue sort order | -| ↳ `teamId` | string | Team ID | -| ↳ `stateId` | string | Workflow state ID | -| ↳ `assigneeId` | string | Assignee user ID | -| ↳ `creatorId` | string | Creator user ID | -| ↳ `projectId` | string | Project ID | -| ↳ `cycleId` | string | Cycle ID | -| ↳ `parentId` | string | Parent issue ID \(for sub-issues\) | -| ↳ `labelIds` | array | Array of label IDs | -| ↳ `subscriberIds` | array | Array of subscriber user IDs | -| ↳ `url` | string | Issue URL | -| ↳ `branchName` | string | Git branch name | -| ↳ `customerTicketCount` | number | Number of customer tickets | -| ↳ `dueDate` | string | Issue due date | -| ↳ `snoozedUntilAt` | string | Snoozed until timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `canceledAt` | string | Canceled timestamp | -| ↳ `completedAt` | string | Completed timestamp | -| ↳ `startedAt` | string | Started timestamp | -| ↳ `triagedAt` | string | Triaged timestamp | -| ↳ `createdAt` | string | Issue creation timestamp | -| ↳ `updatedAt` | string | Issue last update timestamp | -| ↳ `autoArchivedAt` | string | Auto-archived timestamp | -| ↳ `autoClosedAt` | string | Auto-closed timestamp | -| ↳ `previousIdentifiers` | array | Array of previous issue identifiers \(when an issue is moved between teams\) | -| ↳ `integrationSourceType` | string | Integration source type \(if created from an integration\) | -| ↳ `slaStartedAt` | string | SLA timer started timestamp | -| ↳ `slaBreachesAt` | string | SLA breach timestamp | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -399,55 +137,6 @@ Trigger workflow when an issue is updated in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(Issue\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Issue ID | -| ↳ `title` | string | Issue title | -| ↳ `description` | string | Issue description | -| ↳ `identifier` | string | Issue identifier \(e.g., ENG-123\) | -| ↳ `number` | number | Issue number | -| ↳ `priority` | number | Issue priority \(0 = None, 1 = Urgent, 2 = High, 3 = Medium, 4 = Low\) | -| ↳ `estimate` | number | Issue estimate | -| ↳ `sortOrder` | number | Issue sort order | -| ↳ `teamId` | string | Team ID | -| ↳ `stateId` | string | Workflow state ID | -| ↳ `assigneeId` | string | Assignee user ID | -| ↳ `creatorId` | string | Creator user ID | -| ↳ `projectId` | string | Project ID | -| ↳ `cycleId` | string | Cycle ID | -| ↳ `parentId` | string | Parent issue ID \(for sub-issues\) | -| ↳ `labelIds` | array | Array of label IDs | -| ↳ `subscriberIds` | array | Array of subscriber user IDs | -| ↳ `url` | string | Issue URL | -| ↳ `branchName` | string | Git branch name | -| ↳ `customerTicketCount` | number | Number of customer tickets | -| ↳ `dueDate` | string | Issue due date | -| ↳ `snoozedUntilAt` | string | Snoozed until timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `canceledAt` | string | Canceled timestamp | -| ↳ `completedAt` | string | Completed timestamp | -| ↳ `startedAt` | string | Started timestamp | -| ↳ `triagedAt` | string | Triaged timestamp | -| ↳ `createdAt` | string | Issue creation timestamp | -| ↳ `updatedAt` | string | Issue last update timestamp | -| ↳ `autoArchivedAt` | string | Auto-archived timestamp | -| ↳ `autoClosedAt` | string | Auto-closed timestamp | -| ↳ `previousIdentifiers` | array | Array of previous issue identifiers \(when an issue is moved between teams\) | -| ↳ `integrationSourceType` | string | Integration source type \(if created from an integration\) | -| ↳ `slaStartedAt` | string | SLA timer started timestamp | -| ↳ `slaBreachesAt` | string | SLA breach timestamp | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -462,32 +151,6 @@ Trigger workflow when a new label is created in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(IssueLabel\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Label ID | -| ↳ `name` | string | Label name | -| ↳ `description` | string | Label description | -| ↳ `color` | string | Label color \(hex code\) | -| ↳ `organizationId` | string | Organization ID | -| ↳ `teamId` | string | Team ID \(if team-specific label\) | -| ↳ `creatorId` | string | Creator user ID | -| ↳ `isGroup` | boolean | Whether this is a label group | -| ↳ `parentId` | string | Parent label ID \(for nested labels\) | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `createdAt` | string | Label creation timestamp | -| ↳ `updatedAt` | string | Label last update timestamp | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -502,32 +165,6 @@ Trigger workflow when a label is updated in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(IssueLabel\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Label ID | -| ↳ `name` | string | Label name | -| ↳ `description` | string | Label description | -| ↳ `color` | string | Label color \(hex code\) | -| ↳ `organizationId` | string | Organization ID | -| ↳ `teamId` | string | Team ID \(if team-specific label\) | -| ↳ `creatorId` | string | Creator user ID | -| ↳ `isGroup` | boolean | Whether this is a label group | -| ↳ `parentId` | string | Parent label ID \(for nested labels\) | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `createdAt` | string | Label creation timestamp | -| ↳ `updatedAt` | string | Label last update timestamp | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -542,46 +179,6 @@ Trigger workflow when a new project is created in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(Project\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Project ID | -| ↳ `name` | string | Project name | -| ↳ `description` | string | Project description | -| ↳ `icon` | string | Project icon | -| ↳ `color` | string | Project color | -| ↳ `state` | string | Project state \(planned, started, completed, canceled, backlog\) | -| ↳ `slugId` | string | Project slug ID | -| ↳ `url` | string | Project URL | -| ↳ `leadId` | string | Project lead user ID | -| ↳ `creatorId` | string | Creator user ID | -| ↳ `memberIds` | array | Array of member user IDs | -| ↳ `teamIds` | array | Array of team IDs | -| ↳ `priority` | number | Project priority | -| ↳ `sortOrder` | number | Project sort order | -| ↳ `startDate` | string | Project start date | -| ↳ `targetDate` | string | Project target date | -| ↳ `startedAt` | string | Started timestamp | -| ↳ `completedAt` | string | Completed timestamp | -| ↳ `canceledAt` | string | Canceled timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `createdAt` | string | Project creation timestamp | -| ↳ `updatedAt` | string | Project last update timestamp | -| ↳ `progress` | number | Project progress \(0-1\) | -| ↳ `scope` | number | Project scope estimate | -| ↳ `statusId` | string | Project status ID | -| ↳ `bodyData` | object | Project body data \(rich text content\) | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -596,29 +193,6 @@ Trigger workflow when a new project update is posted in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(ProjectUpdate\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Project update ID | -| ↳ `body` | string | Update body content | -| ↳ `url` | string | Project update URL | -| ↳ `projectId` | string | Project ID | -| ↳ `userId` | string | User ID of the author | -| ↳ `health` | string | Project health \(onTrack, atRisk, offTrack\) | -| ↳ `editedAt` | string | Last edited timestamp | -| ↳ `createdAt` | string | Update creation timestamp | -| ↳ `updatedAt` | string | Update last update timestamp | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- @@ -633,46 +207,6 @@ Trigger workflow when a project is updated in Linear | `apiKey` | string | Yes | API Key | | `teamId` | string | No | Team ID | -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `action` | string | Action performed \(create, update, remove\) | -| `type` | string | Entity type \(Project\) | -| `webhookId` | string | Webhook ID | -| `webhookTimestamp` | number | Webhook timestamp \(milliseconds\) | -| `organizationId` | string | Organization ID | -| `createdAt` | string | Event creation timestamp | -| `url` | string | URL of the subject entity in Linear \(top-level webhook payload\) | -| `data` | object | data output from the tool | -| ↳ `id` | string | Project ID | -| ↳ `name` | string | Project name | -| ↳ `description` | string | Project description | -| ↳ `icon` | string | Project icon | -| ↳ `color` | string | Project color | -| ↳ `state` | string | Project state \(planned, started, completed, canceled, backlog\) | -| ↳ `slugId` | string | Project slug ID | -| ↳ `url` | string | Project URL | -| ↳ `leadId` | string | Project lead user ID | -| ↳ `creatorId` | string | Creator user ID | -| ↳ `memberIds` | array | Array of member user IDs | -| ↳ `teamIds` | array | Array of team IDs | -| ↳ `priority` | number | Project priority | -| ↳ `sortOrder` | number | Project sort order | -| ↳ `startDate` | string | Project start date | -| ↳ `targetDate` | string | Project target date | -| ↳ `startedAt` | string | Started timestamp | -| ↳ `completedAt` | string | Completed timestamp | -| ↳ `canceledAt` | string | Canceled timestamp | -| ↳ `archivedAt` | string | Archived timestamp | -| ↳ `createdAt` | string | Project creation timestamp | -| ↳ `updatedAt` | string | Project last update timestamp | -| ↳ `progress` | number | Project progress \(0-1\) | -| ↳ `scope` | number | Project scope estimate | -| ↳ `statusId` | string | Project status ID | -| ↳ `bodyData` | object | Project body data \(rich text content\) | -| `updatedFrom` | object | Previous values for changed fields \(only present on update\) | - --- diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 552ab0335d5..6cdced211a2 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -4662,6 +4662,10 @@ "name": "Draft Email", "description": "Draft emails using Gmail" }, + { + "name": "Edit Draft", + "description": "Update an existing Gmail draft in place without deleting and recreating it." + }, { "name": "Search Email", "description": "Search emails in Gmail" @@ -4699,7 +4703,7 @@ "description": "Remove label(s) from a Gmail message" } ], - "operationCount": 12, + "operationCount": 13, "triggers": [ { "id": "gmail_poller", diff --git a/apps/sim/app/api/tools/gmail/edit-draft/route.ts b/apps/sim/app/api/tools/gmail/edit-draft/route.ts new file mode 100644 index 00000000000..547124b94cb --- /dev/null +++ b/apps/sim/app/api/tools/gmail/edit-draft/route.ts @@ -0,0 +1,180 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { gmailEditDraftContract } from '@/lib/api/contracts/google-tools' +import { parseRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' +import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' +import { + base64UrlEncode, + buildMimeMessage, + buildSimpleEmailMessage, + fetchThreadingHeaders, + GMAIL_API_BASE, +} from '@/tools/gmail/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('GmailEditDraftAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Gmail edit draft attempt: ${authResult.error}`) + return NextResponse.json( + { + success: false, + error: authResult.error || 'Authentication required', + }, + { status: 401 } + ) + } + + logger.info( + `[${requestId}] Authenticated Gmail edit draft request via ${authResult.authType}`, + { userId: authResult.userId } + ) + + const parsed = await parseRequest(gmailEditDraftContract, request, {}) + if (!parsed.success) return parsed.response + const validatedData = parsed.data.body + + logger.info(`[${requestId}] Updating Gmail draft`, { + draftId: validatedData.draftId, + to: validatedData.to, + hasAttachments: !!(validatedData.attachments && validatedData.attachments.length > 0), + attachmentCount: validatedData.attachments?.length || 0, + }) + + const threadingHeaders = validatedData.replyToMessageId + ? await fetchThreadingHeaders(validatedData.replyToMessageId, validatedData.accessToken) + : {} + + const originalMessageId = threadingHeaders.messageId + const originalReferences = threadingHeaders.references + const originalSubject = threadingHeaders.subject + + let rawMessage: string | undefined + + if (validatedData.attachments && validatedData.attachments.length > 0) { + const rawAttachments = validatedData.attachments + const attachments = processFilesToUserFiles(rawAttachments, requestId, logger) + + if (attachments.length > 0) { + const totalSize = attachments.reduce((sum, file) => sum + file.size, 0) + const maxSize = 25 * 1024 * 1024 + + if (totalSize > maxSize) { + const sizeMB = (totalSize / (1024 * 1024)).toFixed(2) + return NextResponse.json( + { + success: false, + error: `Total attachment size (${sizeMB}MB) exceeds Gmail's limit of 25MB`, + }, + { status: 400 } + ) + } + + const attachmentBuffers = await Promise.all( + attachments.map(async (file) => { + const buffer = await downloadFileFromStorage(file, requestId, logger) + return { + filename: file.name, + mimeType: file.type || 'application/octet-stream', + content: buffer, + } + }) + ) + + const mimeMessage = buildMimeMessage({ + to: validatedData.to, + cc: validatedData.cc ?? undefined, + bcc: validatedData.bcc ?? undefined, + subject: validatedData.subject || originalSubject || '', + body: validatedData.body, + contentType: validatedData.contentType || 'text', + inReplyTo: originalMessageId, + references: originalReferences, + attachments: attachmentBuffers, + }) + + rawMessage = base64UrlEncode(mimeMessage) + } + } + + if (!rawMessage) { + rawMessage = buildSimpleEmailMessage({ + to: validatedData.to, + cc: validatedData.cc, + bcc: validatedData.bcc, + subject: validatedData.subject || originalSubject, + body: validatedData.body, + contentType: validatedData.contentType || 'text', + inReplyTo: originalMessageId, + references: originalReferences, + }) + } + + const draftMessage: { raw: string; threadId?: string } = { raw: rawMessage } + if (validatedData.threadId) { + draftMessage.threadId = validatedData.threadId + } + + const gmailResponse = await fetch( + `${GMAIL_API_BASE}/drafts/${encodeURIComponent(validatedData.draftId)}`, + { + method: 'PUT', + headers: { + Authorization: `Bearer ${validatedData.accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: validatedData.draftId, + message: draftMessage, + }), + } + ) + + if (!gmailResponse.ok) { + const errorText = await gmailResponse.text() + logger.error(`[${requestId}] Gmail API error:`, errorText) + return NextResponse.json( + { + success: false, + error: `Gmail API error: ${gmailResponse.statusText}`, + }, + { status: gmailResponse.status } + ) + } + + const data = await gmailResponse.json() + + logger.info(`[${requestId}] Draft updated successfully`, { draftId: data.id }) + + return NextResponse.json({ + success: true, + output: { + draftId: data.id ?? null, + messageId: data.message?.id ?? null, + threadId: data.message?.threadId ?? null, + labelIds: data.message?.labelIds ?? null, + }, + }) + } catch (error) { + logger.error(`[${requestId}] Error updating Gmail draft:`, error) + + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : 'Internal server error', + }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/blocks/blocks/gmail.ts b/apps/sim/blocks/blocks/gmail.ts index dd3e6188054..df5fa8f67e9 100644 --- a/apps/sim/blocks/blocks/gmail.ts +++ b/apps/sim/blocks/blocks/gmail.ts @@ -16,6 +16,8 @@ function selectGmailToolId(params: Record): string { return 'gmail_send' case 'draft_gmail': return 'gmail_draft' + case 'edit_draft_gmail': + return 'gmail_edit_draft_v2' case 'search_gmail': return 'gmail_search' case 'read_gmail': @@ -66,6 +68,7 @@ export const GmailBlock: BlockConfig = { { label: 'Send Email', id: 'send_gmail' }, { label: 'Read Email', id: 'read_gmail' }, { label: 'Draft Email', id: 'draft_gmail' }, + { label: 'Edit Draft', id: 'edit_draft_gmail' }, { label: 'Search Email', id: 'search_gmail' }, { label: 'Move Email', id: 'move_gmail' }, { label: 'Mark as Read', id: 'mark_read_gmail' }, @@ -100,13 +103,22 @@ export const GmailBlock: BlockConfig = { required: true, }, ...SERVICE_ACCOUNT_SUBBLOCKS, + // Edit Draft - Draft ID + { + id: 'draftId', + title: 'Draft ID', + type: 'short-input', + placeholder: 'ID of the draft to update', + condition: { field: 'operation', value: 'edit_draft_gmail' }, + required: true, + }, // Send Email Fields { id: 'to', title: 'To', type: 'short-input', placeholder: 'Recipient email address', - condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, + condition: { field: 'operation', value: ['send_gmail', 'draft_gmail', 'edit_draft_gmail'] }, required: true, }, { @@ -114,7 +126,7 @@ export const GmailBlock: BlockConfig = { title: 'Subject', type: 'short-input', placeholder: 'Email subject', - condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, + condition: { field: 'operation', value: ['send_gmail', 'draft_gmail', 'edit_draft_gmail'] }, required: false, wandConfig: { enabled: true, @@ -130,7 +142,7 @@ Return ONLY the subject line - no explanations, no extra text.`, title: 'Body', type: 'long-input', placeholder: 'Email content', - condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, + condition: { field: 'operation', value: ['send_gmail', 'draft_gmail', 'edit_draft_gmail'] }, required: true, wandConfig: { enabled: true, @@ -153,7 +165,7 @@ Return ONLY the email body - no explanations, no extra text.`, { label: 'Plain Text', id: 'text' }, { label: 'HTML', id: 'html' }, ], - condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, + condition: { field: 'operation', value: ['send_gmail', 'draft_gmail', 'edit_draft_gmail'] }, value: () => 'text', required: false, }, @@ -164,7 +176,7 @@ Return ONLY the email body - no explanations, no extra text.`, type: 'file-upload', canonicalParamId: 'attachments', placeholder: 'Upload files to attach', - condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, + condition: { field: 'operation', value: ['send_gmail', 'draft_gmail', 'edit_draft_gmail'] }, mode: 'basic', multiple: true, required: false, @@ -176,7 +188,7 @@ Return ONLY the email body - no explanations, no extra text.`, type: 'short-input', canonicalParamId: 'attachments', placeholder: 'Reference files from previous blocks', - condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, + condition: { field: 'operation', value: ['send_gmail', 'draft_gmail', 'edit_draft_gmail'] }, mode: 'advanced', required: false, }, @@ -186,7 +198,7 @@ Return ONLY the email body - no explanations, no extra text.`, title: 'Thread ID', type: 'short-input', placeholder: 'Thread ID to reply to (for threading)', - condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, + condition: { field: 'operation', value: ['send_gmail', 'draft_gmail', 'edit_draft_gmail'] }, mode: 'advanced', required: false, }, @@ -195,7 +207,7 @@ Return ONLY the email body - no explanations, no extra text.`, title: 'Reply to Message ID', type: 'short-input', placeholder: 'Gmail message ID (not RFC Message-ID) - use the "id" field from results', - condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, + condition: { field: 'operation', value: ['send_gmail', 'draft_gmail', 'edit_draft_gmail'] }, mode: 'advanced', required: false, }, @@ -205,7 +217,7 @@ Return ONLY the email body - no explanations, no extra text.`, title: 'CC', type: 'short-input', placeholder: 'CC recipients (comma-separated)', - condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, + condition: { field: 'operation', value: ['send_gmail', 'draft_gmail', 'edit_draft_gmail'] }, mode: 'advanced', required: false, }, @@ -214,7 +226,7 @@ Return ONLY the email body - no explanations, no extra text.`, title: 'BCC', type: 'short-input', placeholder: 'BCC recipients (comma-separated)', - condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, + condition: { field: 'operation', value: ['send_gmail', 'draft_gmail', 'edit_draft_gmail'] }, mode: 'advanced', required: false, }, @@ -420,6 +432,7 @@ Return ONLY the search query - no explanations, no extra text.`, 'gmail_delete', 'gmail_add_label', 'gmail_remove_label', + 'gmail_edit_draft_v2', ], config: { tool: selectGmailToolId, @@ -496,6 +509,7 @@ Return ONLY the search query - no explanations, no extra text.`, operation: { type: 'string', description: 'Operation to perform' }, oauthCredential: { type: 'string', description: 'Gmail access token' }, // Send operation inputs + draftId: { type: 'string', description: 'Draft ID for edit operations' }, to: { type: 'string', description: 'Recipient email address' }, subject: { type: 'string', description: 'Email subject' }, body: { type: 'string', description: 'Email content' }, @@ -567,6 +581,7 @@ export const GmailV2Block: BlockConfig = { access: [ 'gmail_send_v2', 'gmail_draft_v2', + 'gmail_edit_draft_v2', 'gmail_read_v2', 'gmail_search_v2', 'gmail_move_v2', @@ -604,12 +619,12 @@ export const GmailV2Block: BlockConfig = { draftId: { type: 'string', description: 'Draft ID', - condition: { field: 'operation', value: 'draft_gmail' }, + condition: { field: 'operation', value: ['draft_gmail', 'edit_draft_gmail'] }, }, messageId: { type: 'string', description: 'Gmail message ID for the draft', - condition: { field: 'operation', value: 'draft_gmail' }, + condition: { field: 'operation', value: ['draft_gmail', 'edit_draft_gmail'] }, }, // Trigger outputs (unchanged) diff --git a/apps/sim/lib/api/contracts/google-tools.ts b/apps/sim/lib/api/contracts/google-tools.ts index 107054c52bc..732e301ec69 100644 --- a/apps/sim/lib/api/contracts/google-tools.ts +++ b/apps/sim/lib/api/contracts/google-tools.ts @@ -33,6 +33,10 @@ export const gmailMailBodySchema = z.object({ attachments: RawFileInputArraySchema.optional().nullable(), }) +export const gmailEditDraftBodySchema = gmailMailBodySchema.extend({ + draftId: z.string().min(1, 'Draft ID is required'), +}) + export const googleDriveUploadBodySchema = z.object({ accessToken: googleAccessTokenSchema, fileName: z.string().min(1, 'File name is required'), @@ -86,6 +90,13 @@ export const gmailDraftContract = defineRouteContract({ response: { mode: 'json', schema: toolJsonResponseSchema }, }) +export const gmailEditDraftContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/gmail/edit-draft', + body: gmailEditDraftBodySchema, + response: { mode: 'json', schema: toolJsonResponseSchema }, +}) + export const gmailMarkReadContract = defineRouteContract({ method: 'POST', path: '/api/tools/gmail/mark-read', @@ -153,6 +164,7 @@ export type GmailAddLabelBody = ContractBodyInput export type GmailArchiveBody = ContractBodyInput export type GmailDeleteBody = ContractBodyInput export type GmailDraftBody = ContractBodyInput +export type GmailEditDraftBody = ContractBodyInput export type GmailMarkReadBody = ContractBodyInput export type GmailMarkUnreadBody = ContractBodyInput export type GmailMoveBody = ContractBodyInput @@ -169,6 +181,7 @@ export type GmailAddLabelResponse = ContractJsonResponse export type GmailDeleteResponse = ContractJsonResponse export type GmailDraftResponse = ContractJsonResponse +export type GmailEditDraftResponse = ContractJsonResponse export type GmailMarkReadResponse = ContractJsonResponse export type GmailMarkUnreadResponse = ContractJsonResponse export type GmailMoveResponse = ContractJsonResponse diff --git a/apps/sim/tools/gmail/edit_draft.ts b/apps/sim/tools/gmail/edit_draft.ts new file mode 100644 index 00000000000..7266ebb95fb --- /dev/null +++ b/apps/sim/tools/gmail/edit_draft.ts @@ -0,0 +1,162 @@ +import type { ToolConfig } from '@/tools/types' + +interface GmailEditDraftParams { + accessToken: string + draftId: string + to: string + subject?: string + body: string + contentType?: string + threadId?: string + replyToMessageId?: string + cc?: string + bcc?: string + attachments?: unknown +} + +interface GmailEditDraftResponse { + success: boolean + output: { + draftId?: string + messageId?: string + threadId?: string + labelIds?: string[] + } +} + +export const gmailEditDraftV2Tool: ToolConfig = { + id: 'gmail_edit_draft_v2', + name: 'Gmail Edit Draft', + description: 'Update an existing Gmail draft in place without deleting and recreating it.', + version: '2.0.0', + + oauth: { + required: true, + provider: 'google-email', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'Access token for Gmail API', + }, + draftId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the draft to update (from Gmail List Drafts or Gmail Get Draft)', + }, + to: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Recipient email address', + }, + subject: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Email subject', + }, + body: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email body content', + }, + contentType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Content type for the email body (text or html)', + }, + threadId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Thread ID to associate the draft with (for threading)', + }, + replyToMessageId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Gmail message ID to reply to - use the "id" field from Gmail Read results (not the RFC "messageId")', + }, + cc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'CC recipients (comma-separated)', + }, + bcc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'BCC recipients (comma-separated)', + }, + attachments: { + type: 'file[]', + required: false, + visibility: 'user-only', + description: 'Files to attach to the email draft', + }, + }, + + request: { + url: '/api/tools/gmail/edit-draft', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params: GmailEditDraftParams) => ({ + accessToken: params.accessToken, + draftId: params.draftId?.trim(), + to: params.to, + subject: params.subject, + body: params.body, + contentType: params.contentType || 'text', + threadId: params.threadId, + replyToMessageId: params.replyToMessageId, + cc: params.cc, + bcc: params.bcc, + attachments: params.attachments, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok || !data.success) { + return { + success: false, + output: {}, + error: data.error || 'Failed to update draft', + } + } + + return { + success: true, + output: { + draftId: data.output?.draftId ?? null, + messageId: data.output?.messageId ?? null, + threadId: data.output?.threadId ?? null, + labelIds: data.output?.labelIds ?? null, + }, + } + }, + + outputs: { + draftId: { type: 'string', description: 'Draft ID', optional: true }, + messageId: { type: 'string', description: 'Gmail message ID for the draft', optional: true }, + threadId: { type: 'string', description: 'Gmail thread ID', optional: true }, + labelIds: { + type: 'array', + items: { type: 'string' }, + description: 'Email labels', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/gmail/index.ts b/apps/sim/tools/gmail/index.ts index 6869a2e5f6a..8c429036d2a 100644 --- a/apps/sim/tools/gmail/index.ts +++ b/apps/sim/tools/gmail/index.ts @@ -5,6 +5,7 @@ import { gmailDeleteTool, gmailDeleteV2Tool } from '@/tools/gmail/delete' import { gmailDeleteDraftV2Tool } from '@/tools/gmail/delete_draft' import { gmailDeleteLabelV2Tool } from '@/tools/gmail/delete_label' import { gmailDraftTool, gmailDraftV2Tool } from '@/tools/gmail/draft' +import { gmailEditDraftV2Tool } from '@/tools/gmail/edit_draft' import { gmailGetDraftV2Tool } from '@/tools/gmail/get_draft' import { gmailGetThreadV2Tool } from '@/tools/gmail/get_thread' import { gmailListDraftsV2Tool } from '@/tools/gmail/list_drafts' @@ -20,6 +21,7 @@ import { gmailSendTool, gmailSendV2Tool } from '@/tools/gmail/send' import { gmailTrashThreadV2Tool } from '@/tools/gmail/trash_thread' import { gmailUnarchiveTool, gmailUnarchiveV2Tool } from '@/tools/gmail/unarchive' import { gmailUntrashThreadV2Tool } from '@/tools/gmail/untrash_thread' +import { gmailUpdateLabelV2Tool } from '@/tools/gmail/update_label' export { gmailSendTool, @@ -48,6 +50,7 @@ export { gmailRemoveLabelV2Tool, gmailListDraftsV2Tool, gmailGetDraftV2Tool, + gmailEditDraftV2Tool, gmailDeleteDraftV2Tool, gmailCreateLabelV2Tool, gmailDeleteLabelV2Tool, @@ -56,4 +59,5 @@ export { gmailListThreadsV2Tool, gmailTrashThreadV2Tool, gmailUntrashThreadV2Tool, + gmailUpdateLabelV2Tool, } diff --git a/apps/sim/tools/gmail/update_label.ts b/apps/sim/tools/gmail/update_label.ts new file mode 100644 index 00000000000..2adc6d27298 --- /dev/null +++ b/apps/sim/tools/gmail/update_label.ts @@ -0,0 +1,136 @@ +import { GMAIL_API_BASE } from '@/tools/gmail/utils' +import type { ToolConfig } from '@/tools/types' + +/** + * Tool-only (not exposed in the Gmail block UI). Mirrors the existing + * `gmail_create_label_v2` / `gmail_delete_label_v2` / `gmail_list_labels_v2` + * pattern — these are programmatic/agent-facing tools used by Mothership + * and MCP, not visual workflow operations. + */ + +interface GmailUpdateLabelParams { + accessToken: string + labelId: string + name?: string + messageListVisibility?: string + labelListVisibility?: string +} + +interface GmailUpdateLabelResponse { + success: boolean + output: { + id: string + name?: string + messageListVisibility?: string | null + labelListVisibility?: string | null + type?: string | null + } +} + +export const gmailUpdateLabelV2Tool: ToolConfig = + { + id: 'gmail_update_label_v2', + name: 'Gmail Update Label', + description: + 'Update a Gmail label in place (rename or change visibility) without recreating it', + version: '2.0.0', + + oauth: { + required: true, + provider: 'google-email', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'Access token for Gmail API', + }, + labelId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the label to update (from Gmail List Labels)', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New display name for the label', + }, + messageListVisibility: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Visibility of messages with this label in the message list (show or hide)', + }, + labelListVisibility: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Visibility of the label in the label list (labelShow, labelShowIfUnread, or labelHide)', + }, + }, + + request: { + url: (params: GmailUpdateLabelParams) => + `${GMAIL_API_BASE}/labels/${encodeURIComponent(params.labelId.trim())}`, + method: 'PATCH', + headers: (params: GmailUpdateLabelParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params: GmailUpdateLabelParams) => { + const body: Record = {} + if (params.name) body.name = params.name + if (params.messageListVisibility) { + body.messageListVisibility = params.messageListVisibility + } + if (params.labelListVisibility) { + body.labelListVisibility = params.labelListVisibility + } + return body + }, + }, + + transformResponse: async (response: Response, params?: GmailUpdateLabelParams) => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + output: { id: params?.labelId ?? '' }, + error: data.error?.message || 'Failed to update label', + } + } + + return { + success: true, + output: { + id: data.id, + name: data.name ?? null, + messageListVisibility: data.messageListVisibility ?? null, + labelListVisibility: data.labelListVisibility ?? null, + type: data.type ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Label ID' }, + name: { type: 'string', description: 'Label display name', optional: true }, + messageListVisibility: { + type: 'string', + description: 'Visibility of messages with this label', + optional: true, + }, + labelListVisibility: { + type: 'string', + description: 'Visibility of the label in the label list', + optional: true, + }, + type: { type: 'string', description: 'Label type (system or user)', optional: true }, + }, + } diff --git a/apps/sim/tools/knowledge/search.ts b/apps/sim/tools/knowledge/search.ts index 276241caab4..7f0ee99e933 100644 --- a/apps/sim/tools/knowledge/search.ts +++ b/apps/sim/tools/knowledge/search.ts @@ -1,4 +1,4 @@ -import { DEFAULT_RERANKER_MODEL, SUPPORTED_RERANKER_MODELS } from '@/lib/knowledge/reranker-models' +import { DEFAULT_RERANKER_MODEL } from '@/lib/knowledge/reranker-models' import type { KnowledgeSearchResponse } from '@/tools/knowledge/types' import { enrichKBTagFiltersSchema } from '@/tools/schema-enrichers' import { parseTagFilters } from '@/tools/shared/tags' @@ -52,7 +52,8 @@ export const knowledgeSearchTool: ToolConfig = { type: 'string', required: false, visibility: 'user-only', - description: `Cohere rerank model to use (one of: ${SUPPORTED_RERANKER_MODELS.join(', ')})`, + description: + 'Cohere rerank model to use (one of: rerank-v4.0-pro, rerank-v4.0-fast, rerank-v3.5)', }, }, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 8ee2f4f45f7..ad7dd384867 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -862,6 +862,7 @@ import { gmailDeleteV2Tool, gmailDraftTool, gmailDraftV2Tool, + gmailEditDraftV2Tool, gmailGetDraftV2Tool, gmailGetThreadV2Tool, gmailListDraftsV2Tool, @@ -885,6 +886,7 @@ import { gmailUnarchiveTool, gmailUnarchiveV2Tool, gmailUntrashThreadV2Tool, + gmailUpdateLabelV2Tool, } from '@/tools/gmail' import { gongAggregateActivityTool, @@ -3612,6 +3614,7 @@ export const tools: Record = { gmail_create_label_v2: gmailCreateLabelV2Tool, gmail_delete_draft_v2: gmailDeleteDraftV2Tool, gmail_delete_label_v2: gmailDeleteLabelV2Tool, + gmail_edit_draft_v2: gmailEditDraftV2Tool, gmail_get_draft_v2: gmailGetDraftV2Tool, gmail_get_thread_v2: gmailGetThreadV2Tool, gmail_list_drafts_v2: gmailListDraftsV2Tool, @@ -3619,6 +3622,7 @@ export const tools: Record = { gmail_list_threads_v2: gmailListThreadsV2Tool, gmail_trash_thread_v2: gmailTrashThreadV2Tool, gmail_untrash_thread_v2: gmailUntrashThreadV2Tool, + gmail_update_label_v2: gmailUpdateLabelV2Tool, whatsapp_send_message: whatsappSendMessageTool, x_write: xWriteTool, x_read: xReadTool, diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index 0bb40116afa..cfcfee63182 100644 --- a/scripts/check-api-validation-contracts.ts +++ b/scripts/check-api-validation-contracts.ts @@ -9,8 +9,8 @@ const QUERY_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/queries') const SELECTOR_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/selectors') const BASELINE = { - totalRoutes: 715, - zodRoutes: 715, + totalRoutes: 716, + zodRoutes: 716, nonZodRoutes: 0, } as const