mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
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:
@@ -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 }
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
}
|
||||
@@ -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)',
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user