feat(gmail): add edit draft and update label tools (#4374)

* feat(gmail): add edit draft and update label tools

* fix(gmail): correct legacy block access list and docs heading for edit_draft_v2

* fix(gmail): use shared contract for edit-draft route

* regen docs

* fix(knowledge): inline reranker model list in description for doc generator

* resolve
This commit is contained in:
Waleed
2026-04-30 19:51:02 -07:00
committed by GitHub
parent be9c959f1a
commit 8b6307aea4
13 changed files with 564 additions and 484 deletions
+28
View File
@@ -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.
@@ -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 |
@@ -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\) |
---
@@ -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",
@@ -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 }
)
}
})
+27 -12
View File
@@ -16,6 +16,8 @@ function selectGmailToolId(params: Record<string, any>): 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<GmailToolResponse> = {
{ 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<GmailToolResponse> = {
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<GmailToolResponse> = {
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<GmailToolResponse> = {
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<GmailToolResponse> = {
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)
@@ -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<typeof gmailAddLabelContract>
export type GmailArchiveBody = ContractBodyInput<typeof gmailArchiveContract>
export type GmailDeleteBody = ContractBodyInput<typeof gmailDeleteContract>
export type GmailDraftBody = ContractBodyInput<typeof gmailDraftContract>
export type GmailEditDraftBody = ContractBodyInput<typeof gmailEditDraftContract>
export type GmailMarkReadBody = ContractBodyInput<typeof gmailMarkReadContract>
export type GmailMarkUnreadBody = ContractBodyInput<typeof gmailMarkUnreadContract>
export type GmailMoveBody = ContractBodyInput<typeof gmailMoveContract>
@@ -169,6 +181,7 @@ export type GmailAddLabelResponse = ContractJsonResponse<typeof gmailAddLabelCon
export type GmailArchiveResponse = ContractJsonResponse<typeof gmailArchiveContract>
export type GmailDeleteResponse = ContractJsonResponse<typeof gmailDeleteContract>
export type GmailDraftResponse = ContractJsonResponse<typeof gmailDraftContract>
export type GmailEditDraftResponse = ContractJsonResponse<typeof gmailEditDraftContract>
export type GmailMarkReadResponse = ContractJsonResponse<typeof gmailMarkReadContract>
export type GmailMarkUnreadResponse = ContractJsonResponse<typeof gmailMarkUnreadContract>
export type GmailMoveResponse = ContractJsonResponse<typeof gmailMoveContract>
+162
View File
@@ -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<GmailEditDraftParams, GmailEditDraftResponse> = {
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,
},
},
}
+4
View File
@@ -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,
}
+136
View File
@@ -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<GmailUpdateLabelParams, GmailUpdateLabelResponse> =
{
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<string, string> = {}
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 },
},
}
+3 -2
View File
@@ -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<any, KnowledgeSearchResponse> = {
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)',
},
},
+4
View File
@@ -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<string, ToolConfig> = {
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<string, ToolConfig> = {
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,
+2 -2
View File
@@ -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