From e45fbe0184b17d0c1ccd8a1a68ac4c5ba225a1a0 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 31 Mar 2026 16:44:49 -0700 Subject: [PATCH] improvement(attio): validate integration, fix event bug, add missing tool and triggers (#3872) * improvement(attio): validate integration, fix event bug, add missing tool and triggers * fix(attio): wire new trigger extractors into dispatcher, trim targetUrl Add extractAttioListData and extractAttioWorkspaceMemberData dispatch branches in utils.server.ts so the four new triggers return correct outputs instead of falling through to generic extraction. Also add missing .trim() on targetUrl in update_webhook. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- apps/docs/content/docs/en/tools/attio.mdx | 33 +++++++- .../integrations/data/integrations.json | 28 ++++++- apps/sim/blocks/blocks/attio.ts | 18 ++++- apps/sim/lib/webhooks/utils.server.ts | 12 +++ apps/sim/tools/attio/assert_record.ts | 2 +- apps/sim/tools/attio/create_list_entry.ts | 2 +- apps/sim/tools/attio/create_record.ts | 2 +- apps/sim/tools/attio/delete_comment.ts | 2 +- apps/sim/tools/attio/delete_list_entry.ts | 3 +- apps/sim/tools/attio/delete_note.ts | 2 +- apps/sim/tools/attio/delete_record.ts | 2 +- apps/sim/tools/attio/delete_task.ts | 2 +- apps/sim/tools/attio/delete_webhook.ts | 2 +- apps/sim/tools/attio/get_comment.ts | 2 +- apps/sim/tools/attio/get_list.ts | 2 +- apps/sim/tools/attio/get_list_entry.ts | 3 +- apps/sim/tools/attio/get_member.ts | 2 +- apps/sim/tools/attio/get_note.ts | 2 +- apps/sim/tools/attio/get_object.ts | 2 +- apps/sim/tools/attio/get_record.ts | 2 +- apps/sim/tools/attio/get_task.ts | 78 +++++++++++++++++++ apps/sim/tools/attio/get_thread.ts | 2 +- apps/sim/tools/attio/get_webhook.ts | 2 +- apps/sim/tools/attio/index.ts | 1 + apps/sim/tools/attio/list_records.ts | 2 +- apps/sim/tools/attio/query_list_entries.ts | 2 +- apps/sim/tools/attio/types.ts | 21 +++++ apps/sim/tools/attio/update_list.ts | 2 +- apps/sim/tools/attio/update_list_entry.ts | 3 +- apps/sim/tools/attio/update_object.ts | 2 +- apps/sim/tools/attio/update_record.ts | 2 +- apps/sim/tools/attio/update_task.ts | 2 +- apps/sim/tools/attio/update_webhook.ts | 17 ++-- apps/sim/tools/registry.ts | 2 + apps/sim/triggers/attio/index.ts | 4 + apps/sim/triggers/attio/list_created.ts | 29 +++++++ apps/sim/triggers/attio/list_deleted.ts | 29 +++++++ apps/sim/triggers/attio/list_updated.ts | 29 +++++++ apps/sim/triggers/attio/utils.ts | 75 +++++++++++++++++- .../attio/workspace_member_created.ts | 29 +++++++ apps/sim/triggers/registry.ts | 8 ++ 41 files changed, 422 insertions(+), 44 deletions(-) create mode 100644 apps/sim/tools/attio/get_task.ts create mode 100644 apps/sim/triggers/attio/list_created.ts create mode 100644 apps/sim/triggers/attio/list_deleted.ts create mode 100644 apps/sim/triggers/attio/list_updated.ts create mode 100644 apps/sim/triggers/attio/workspace_member_created.ts diff --git a/apps/docs/content/docs/en/tools/attio.mdx b/apps/docs/content/docs/en/tools/attio.mdx index 85ad63126b..5a36922ee4 100644 --- a/apps/docs/content/docs/en/tools/attio.mdx +++ b/apps/docs/content/docs/en/tools/attio.mdx @@ -359,6 +359,35 @@ List tasks in Attio, optionally filtered by record, assignee, or completion stat | ↳ `createdAt` | string | When the task was created | | `count` | number | Number of tasks returned | +### `attio_get_task` + +Get a single task by ID from Attio + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `taskId` | string | Yes | The ID of the task to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `taskId` | string | The task ID | +| `content` | string | The task content | +| `deadlineAt` | string | The task deadline | +| `isCompleted` | boolean | Whether the task is completed | +| `linkedRecords` | array | Records linked to this task | +| ↳ `targetObjectId` | string | The linked object ID | +| ↳ `targetRecordId` | string | The linked record ID | +| `assignees` | array | Task assignees | +| ↳ `type` | string | The assignee actor type \(e.g. workspace-member\) | +| ↳ `id` | string | The assignee actor ID | +| `createdByActor` | object | The actor who created this task | +| ↳ `type` | string | The actor type \(e.g. workspace-member, api-token, system\) | +| ↳ `id` | string | The actor ID | +| `createdAt` | string | When the task was created | + ### `attio_create_task` Create a task in Attio @@ -1012,8 +1041,8 @@ Update a webhook in Attio (target URL and/or subscriptions) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `webhookId` | string | Yes | The webhook ID to update | -| `targetUrl` | string | Yes | HTTPS target URL for webhook delivery | -| `subscriptions` | string | Yes | JSON array of subscriptions, e.g. \[\{"event_type":"note.created"\}\] | +| `targetUrl` | string | No | HTTPS target URL for webhook delivery | +| `subscriptions` | string | No | JSON array of subscriptions, e.g. \[\{"event_type":"note.created"\}\] | #### Output diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 2f25c4257b..f36c23fbb4 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -926,6 +926,10 @@ "name": "List Tasks", "description": "List tasks in Attio, optionally filtered by record, assignee, or completion status" }, + { + "name": "Get Task", + "description": "Get a single task by ID from Attio" + }, { "name": "Create Task", "description": "Create a task in Attio" @@ -1039,7 +1043,7 @@ "description": "Delete a webhook from Attio" } ], - "operationCount": 40, + "operationCount": 41, "triggers": [ { "id": "attio_record_created", @@ -1126,13 +1130,33 @@ "name": "Attio List Entry Deleted", "description": "Trigger workflow when a list entry is deleted in Attio" }, + { + "id": "attio_list_created", + "name": "Attio List Created", + "description": "Trigger workflow when a list is created in Attio" + }, + { + "id": "attio_list_updated", + "name": "Attio List Updated", + "description": "Trigger workflow when a list is updated in Attio" + }, + { + "id": "attio_list_deleted", + "name": "Attio List Deleted", + "description": "Trigger workflow when a list is deleted in Attio" + }, + { + "id": "attio_workspace_member_created", + "name": "Attio Workspace Member Created", + "description": "Trigger workflow when a new member is added to the Attio workspace" + }, { "id": "attio_webhook", "name": "Attio Webhook (All Events)", "description": "Trigger workflow on any Attio webhook event" } ], - "triggerCount": 18, + "triggerCount": 22, "authType": "oauth", "category": "tools", "integrationType": "crm", diff --git a/apps/sim/blocks/blocks/attio.ts b/apps/sim/blocks/blocks/attio.ts index 21ee5ffdf8..9a6af86f6a 100644 --- a/apps/sim/blocks/blocks/attio.ts +++ b/apps/sim/blocks/blocks/attio.ts @@ -36,6 +36,7 @@ export const AttioBlock: BlockConfig = { { label: 'Create Note', id: 'create_note' }, { label: 'Delete Note', id: 'delete_note' }, { label: 'List Tasks', id: 'list_tasks' }, + { label: 'Get Task', id: 'get_task' }, { label: 'Create Task', id: 'create_task' }, { label: 'Update Task', id: 'update_task' }, { label: 'Delete Task', id: 'delete_task' }, @@ -490,8 +491,8 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. title: 'Task ID', type: 'short-input', placeholder: 'Enter the task ID', - condition: { field: 'operation', value: ['update_task', 'delete_task'] }, - required: { field: 'operation', value: ['update_task', 'delete_task'] }, + condition: { field: 'operation', value: ['get_task', 'update_task', 'delete_task'] }, + required: { field: 'operation', value: ['get_task', 'update_task', 'delete_task'] }, }, { id: 'taskFilterObject', @@ -944,7 +945,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ type: 'short-input', placeholder: 'https://example.com/webhook', condition: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, - required: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, + required: { field: 'operation', value: 'create_webhook' }, }, { id: 'webhookSubscriptions', @@ -952,7 +953,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ type: 'code', placeholder: '[{"event_type":"record.created","filter":{"object_id":"..."}}]', condition: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, - required: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, + required: { field: 'operation', value: 'create_webhook' }, wandConfig: { enabled: true, maintainHistory: true, @@ -1040,6 +1041,10 @@ workspace-member.created ...getTrigger('attio_list_entry_created').subBlocks, ...getTrigger('attio_list_entry_updated').subBlocks, ...getTrigger('attio_list_entry_deleted').subBlocks, + ...getTrigger('attio_list_created').subBlocks, + ...getTrigger('attio_list_updated').subBlocks, + ...getTrigger('attio_list_deleted').subBlocks, + ...getTrigger('attio_workspace_member_created').subBlocks, ...getTrigger('attio_webhook').subBlocks, ], @@ -1063,6 +1068,10 @@ workspace-member.created 'attio_list_entry_created', 'attio_list_entry_updated', 'attio_list_entry_deleted', + 'attio_list_created', + 'attio_list_updated', + 'attio_list_deleted', + 'attio_workspace_member_created', 'attio_webhook', ], }, @@ -1081,6 +1090,7 @@ workspace-member.created 'attio_create_note', 'attio_delete_note', 'attio_list_tasks', + 'attio_get_task', 'attio_create_task', 'attio_update_task', 'attio_delete_task', diff --git a/apps/sim/lib/webhooks/utils.server.ts b/apps/sim/lib/webhooks/utils.server.ts index 3eec55697f..4decf492eb 100644 --- a/apps/sim/lib/webhooks/utils.server.ts +++ b/apps/sim/lib/webhooks/utils.server.ts @@ -1311,6 +1311,8 @@ export async function formatWebhookInput( extractAttioCommentData, extractAttioListEntryData, extractAttioListEntryUpdatedData, + extractAttioListData, + extractAttioWorkspaceMemberData, extractAttioGenericData, } = await import('@/triggers/attio/utils') @@ -1341,6 +1343,16 @@ export async function formatWebhookInput( if (triggerId === 'attio_list_entry_created' || triggerId === 'attio_list_entry_deleted') { return extractAttioListEntryData(body) } + if ( + triggerId === 'attio_list_created' || + triggerId === 'attio_list_updated' || + triggerId === 'attio_list_deleted' + ) { + return extractAttioListData(body) + } + if (triggerId === 'attio_workspace_member_created') { + return extractAttioWorkspaceMemberData(body) + } return extractAttioGenericData(body) } diff --git a/apps/sim/tools/attio/assert_record.ts b/apps/sim/tools/attio/assert_record.ts index 3f1414b102..00864d740a 100644 --- a/apps/sim/tools/attio/assert_record.ts +++ b/apps/sim/tools/attio/assert_record.ts @@ -49,7 +49,7 @@ export const attioAssertRecordTool: ToolConfig - `https://api.attio.com/v2/objects/${params.objectType}/records?matching_attribute=${params.matchingAttribute}`, + `https://api.attio.com/v2/objects/${params.objectType.trim()}/records?matching_attribute=${params.matchingAttribute.trim()}`, method: 'PUT', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/create_list_entry.ts b/apps/sim/tools/attio/create_list_entry.ts index 381e61e1d7..c194b3407b 100644 --- a/apps/sim/tools/attio/create_list_entry.ts +++ b/apps/sim/tools/attio/create_list_entry.ts @@ -53,7 +53,7 @@ export const attioCreateListEntryTool: ToolConfig< }, request: { - url: (params) => `https://api.attio.com/v2/lists/${params.list}/entries`, + url: (params) => `https://api.attio.com/v2/lists/${params.list.trim()}/entries`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/create_record.ts b/apps/sim/tools/attio/create_record.ts index 3589c0072c..4ec2bb13d3 100644 --- a/apps/sim/tools/attio/create_record.ts +++ b/apps/sim/tools/attio/create_record.ts @@ -39,7 +39,7 @@ export const attioCreateRecordTool: ToolConfig `https://api.attio.com/v2/objects/${params.objectType}/records`, + url: (params) => `https://api.attio.com/v2/objects/${params.objectType.trim()}/records`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/delete_comment.ts b/apps/sim/tools/attio/delete_comment.ts index 5fc0aed80b..e78d71e1b3 100644 --- a/apps/sim/tools/attio/delete_comment.ts +++ b/apps/sim/tools/attio/delete_comment.ts @@ -34,7 +34,7 @@ export const attioDeleteCommentTool: ToolConfig< }, request: { - url: (params) => `https://api.attio.com/v2/comments/${params.commentId}`, + url: (params) => `https://api.attio.com/v2/comments/${params.commentId.trim()}`, method: 'DELETE', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/delete_list_entry.ts b/apps/sim/tools/attio/delete_list_entry.ts index 8aede08525..e46da4d42e 100644 --- a/apps/sim/tools/attio/delete_list_entry.ts +++ b/apps/sim/tools/attio/delete_list_entry.ts @@ -40,7 +40,8 @@ export const attioDeleteListEntryTool: ToolConfig< }, request: { - url: (params) => `https://api.attio.com/v2/lists/${params.list}/entries/${params.entryId}`, + url: (params) => + `https://api.attio.com/v2/lists/${params.list.trim()}/entries/${params.entryId.trim()}`, method: 'DELETE', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/delete_note.ts b/apps/sim/tools/attio/delete_note.ts index 2980801cc6..bd5cfe1a05 100644 --- a/apps/sim/tools/attio/delete_note.ts +++ b/apps/sim/tools/attio/delete_note.ts @@ -31,7 +31,7 @@ export const attioDeleteNoteTool: ToolConfig `https://api.attio.com/v2/notes/${params.noteId}`, + url: (params) => `https://api.attio.com/v2/notes/${params.noteId.trim()}`, method: 'DELETE', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/delete_record.ts b/apps/sim/tools/attio/delete_record.ts index 82da6b68c6..cc28440030 100644 --- a/apps/sim/tools/attio/delete_record.ts +++ b/apps/sim/tools/attio/delete_record.ts @@ -39,7 +39,7 @@ export const attioDeleteRecordTool: ToolConfig - `https://api.attio.com/v2/objects/${params.objectType}/records/${params.recordId}`, + `https://api.attio.com/v2/objects/${params.objectType.trim()}/records/${params.recordId.trim()}`, method: 'DELETE', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/delete_task.ts b/apps/sim/tools/attio/delete_task.ts index 02672f6cd3..088f74707c 100644 --- a/apps/sim/tools/attio/delete_task.ts +++ b/apps/sim/tools/attio/delete_task.ts @@ -31,7 +31,7 @@ export const attioDeleteTaskTool: ToolConfig `https://api.attio.com/v2/tasks/${params.taskId}`, + url: (params) => `https://api.attio.com/v2/tasks/${params.taskId.trim()}`, method: 'DELETE', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/delete_webhook.ts b/apps/sim/tools/attio/delete_webhook.ts index 17f1aa6d97..eaacde34eb 100644 --- a/apps/sim/tools/attio/delete_webhook.ts +++ b/apps/sim/tools/attio/delete_webhook.ts @@ -34,7 +34,7 @@ export const attioDeleteWebhookTool: ToolConfig< }, request: { - url: (params) => `https://api.attio.com/v2/webhooks/${params.webhookId}`, + url: (params) => `https://api.attio.com/v2/webhooks/${params.webhookId.trim()}`, method: 'DELETE', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/get_comment.ts b/apps/sim/tools/attio/get_comment.ts index c32aa84f5a..3bfa823697 100644 --- a/apps/sim/tools/attio/get_comment.ts +++ b/apps/sim/tools/attio/get_comment.ts @@ -32,7 +32,7 @@ export const attioGetCommentTool: ToolConfig `https://api.attio.com/v2/comments/${params.commentId}`, + url: (params) => `https://api.attio.com/v2/comments/${params.commentId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/get_list.ts b/apps/sim/tools/attio/get_list.ts index cf6cbb7b0f..440b28e81c 100644 --- a/apps/sim/tools/attio/get_list.ts +++ b/apps/sim/tools/attio/get_list.ts @@ -32,7 +32,7 @@ export const attioGetListTool: ToolConfig `https://api.attio.com/v2/lists/${params.list}`, + url: (params) => `https://api.attio.com/v2/lists/${params.list.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/get_list_entry.ts b/apps/sim/tools/attio/get_list_entry.ts index 8047a953e7..22aa1c4148 100644 --- a/apps/sim/tools/attio/get_list_entry.ts +++ b/apps/sim/tools/attio/get_list_entry.ts @@ -39,7 +39,8 @@ export const attioGetListEntryTool: ToolConfig `https://api.attio.com/v2/lists/${params.list}/entries/${params.entryId}`, + url: (params) => + `https://api.attio.com/v2/lists/${params.list.trim()}/entries/${params.entryId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/get_member.ts b/apps/sim/tools/attio/get_member.ts index 3469856c5b..11f2c08c07 100644 --- a/apps/sim/tools/attio/get_member.ts +++ b/apps/sim/tools/attio/get_member.ts @@ -32,7 +32,7 @@ export const attioGetMemberTool: ToolConfig `https://api.attio.com/v2/workspace_members/${params.memberId}`, + url: (params) => `https://api.attio.com/v2/workspace_members/${params.memberId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/get_note.ts b/apps/sim/tools/attio/get_note.ts index 7637edc40d..0a9ad4a8e7 100644 --- a/apps/sim/tools/attio/get_note.ts +++ b/apps/sim/tools/attio/get_note.ts @@ -32,7 +32,7 @@ export const attioGetNoteTool: ToolConfig `https://api.attio.com/v2/notes/${params.noteId}`, + url: (params) => `https://api.attio.com/v2/notes/${params.noteId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/get_object.ts b/apps/sim/tools/attio/get_object.ts index 5f67495dda..09ecc9c267 100644 --- a/apps/sim/tools/attio/get_object.ts +++ b/apps/sim/tools/attio/get_object.ts @@ -32,7 +32,7 @@ export const attioGetObjectTool: ToolConfig `https://api.attio.com/v2/objects/${params.object}`, + url: (params) => `https://api.attio.com/v2/objects/${params.object.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/get_record.ts b/apps/sim/tools/attio/get_record.ts index 979492c6ae..2ae04ddbad 100644 --- a/apps/sim/tools/attio/get_record.ts +++ b/apps/sim/tools/attio/get_record.ts @@ -39,7 +39,7 @@ export const attioGetRecordTool: ToolConfig - `https://api.attio.com/v2/objects/${params.objectType}/records/${params.recordId}`, + `https://api.attio.com/v2/objects/${params.objectType.trim()}/records/${params.recordId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/get_task.ts b/apps/sim/tools/attio/get_task.ts new file mode 100644 index 0000000000..82ac333861 --- /dev/null +++ b/apps/sim/tools/attio/get_task.ts @@ -0,0 +1,78 @@ +import { createLogger } from '@sim/logger' +import type { ToolConfig } from '@/tools/types' +import type { AttioGetTaskParams, AttioGetTaskResponse } from './types' +import { TASK_OUTPUT_PROPERTIES } from './types' + +const logger = createLogger('AttioGetTask') + +export const attioGetTaskTool: ToolConfig = { + id: 'attio_get_task', + name: 'Attio Get Task', + description: 'Get a single task by ID from Attio', + version: '1.0.0', + + oauth: { + required: true, + provider: 'attio', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The OAuth access token for the Attio API', + }, + taskId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the task to retrieve', + }, + }, + + request: { + url: (params) => `https://api.attio.com/v2/tasks/${params.taskId.trim()}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + if (!response.ok) { + logger.error('Attio API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to get task') + } + const task = data.data + const linkedRecords = (task.linked_records ?? []).map( + (r: { target_object_id?: string; target_record_id?: string }) => ({ + targetObjectId: r.target_object_id ?? null, + targetRecordId: r.target_record_id ?? null, + }) + ) + const assignees = (task.assignees ?? []).map( + (a: { referenced_actor_type?: string; referenced_actor_id?: string }) => ({ + type: a.referenced_actor_type ?? null, + id: a.referenced_actor_id ?? null, + }) + ) + return { + success: true, + output: { + taskId: task.id?.task_id ?? null, + content: task.content_plaintext ?? null, + deadlineAt: task.deadline_at ?? null, + isCompleted: task.is_completed ?? false, + linkedRecords, + assignees, + createdByActor: task.created_by_actor ?? null, + createdAt: task.created_at ?? null, + }, + } + }, + + outputs: TASK_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/attio/get_thread.ts b/apps/sim/tools/attio/get_thread.ts index 83bc0fec3c..62f0a47f1d 100644 --- a/apps/sim/tools/attio/get_thread.ts +++ b/apps/sim/tools/attio/get_thread.ts @@ -32,7 +32,7 @@ export const attioGetThreadTool: ToolConfig `https://api.attio.com/v2/threads/${params.threadId}`, + url: (params) => `https://api.attio.com/v2/threads/${params.threadId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/get_webhook.ts b/apps/sim/tools/attio/get_webhook.ts index 826899187e..7cc76f9e1d 100644 --- a/apps/sim/tools/attio/get_webhook.ts +++ b/apps/sim/tools/attio/get_webhook.ts @@ -32,7 +32,7 @@ export const attioGetWebhookTool: ToolConfig `https://api.attio.com/v2/webhooks/${params.webhookId}`, + url: (params) => `https://api.attio.com/v2/webhooks/${params.webhookId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/index.ts b/apps/sim/tools/attio/index.ts index 7212fc424a..978993b87d 100644 --- a/apps/sim/tools/attio/index.ts +++ b/apps/sim/tools/attio/index.ts @@ -20,6 +20,7 @@ export { attioGetMemberTool } from './get_member' export { attioGetNoteTool } from './get_note' export { attioGetObjectTool } from './get_object' export { attioGetRecordTool } from './get_record' +export { attioGetTaskTool } from './get_task' export { attioGetThreadTool } from './get_thread' export { attioGetWebhookTool } from './get_webhook' export { attioListListsTool } from './list_lists' diff --git a/apps/sim/tools/attio/list_records.ts b/apps/sim/tools/attio/list_records.ts index 7fae18d443..39cfef2834 100644 --- a/apps/sim/tools/attio/list_records.ts +++ b/apps/sim/tools/attio/list_records.ts @@ -56,7 +56,7 @@ export const attioListRecordsTool: ToolConfig `https://api.attio.com/v2/objects/${params.objectType}/records/query`, + url: (params) => `https://api.attio.com/v2/objects/${params.objectType.trim()}/records/query`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/query_list_entries.ts b/apps/sim/tools/attio/query_list_entries.ts index d234c2aec3..6b0574d88b 100644 --- a/apps/sim/tools/attio/query_list_entries.ts +++ b/apps/sim/tools/attio/query_list_entries.ts @@ -60,7 +60,7 @@ export const attioQueryListEntriesTool: ToolConfig< }, request: { - url: (params) => `https://api.attio.com/v2/lists/${params.list}/entries/query`, + url: (params) => `https://api.attio.com/v2/lists/${params.list.trim()}/entries/query`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/types.ts b/apps/sim/tools/attio/types.ts index cb7c726426..0f2bb2a9c2 100644 --- a/apps/sim/tools/attio/types.ts +++ b/apps/sim/tools/attio/types.ts @@ -529,6 +529,26 @@ export interface AttioUpdateTaskResponse extends ToolResponse { } } +/** Params for getting a single task */ +export interface AttioGetTaskParams { + accessToken: string + taskId: string +} + +/** Response for getting a single task */ +export interface AttioGetTaskResponse extends ToolResponse { + output: { + taskId: string | null + content: string | null + deadlineAt: string | null + isCompleted: boolean + linkedRecords: Array<{ targetObjectId: string | null; targetRecordId: string | null }> + assignees: Array<{ type: string | null; id: string | null }> + createdByActor: unknown + createdAt: string | null + } +} + /** Response for deleting a task */ export interface AttioDeleteTaskResponse extends ToolResponse { output: { @@ -1093,6 +1113,7 @@ export type AttioResponse = | AttioListTasksResponse | AttioCreateTaskResponse | AttioUpdateTaskResponse + | AttioGetTaskResponse | AttioDeleteTaskResponse | AttioListObjectsResponse | AttioGetObjectResponse diff --git a/apps/sim/tools/attio/update_list.ts b/apps/sim/tools/attio/update_list.ts index e54176a128..e36f74b2ec 100644 --- a/apps/sim/tools/attio/update_list.ts +++ b/apps/sim/tools/attio/update_list.ts @@ -58,7 +58,7 @@ export const attioUpdateListTool: ToolConfig `https://api.attio.com/v2/lists/${params.list}`, + url: (params) => `https://api.attio.com/v2/lists/${params.list.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/update_list_entry.ts b/apps/sim/tools/attio/update_list_entry.ts index d6da7dc01e..760e960cdf 100644 --- a/apps/sim/tools/attio/update_list_entry.ts +++ b/apps/sim/tools/attio/update_list_entry.ts @@ -47,7 +47,8 @@ export const attioUpdateListEntryTool: ToolConfig< }, request: { - url: (params) => `https://api.attio.com/v2/lists/${params.list}/entries/${params.entryId}`, + url: (params) => + `https://api.attio.com/v2/lists/${params.list.trim()}/entries/${params.entryId.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/update_object.ts b/apps/sim/tools/attio/update_object.ts index 1b8c0024a9..0136bae05d 100644 --- a/apps/sim/tools/attio/update_object.ts +++ b/apps/sim/tools/attio/update_object.ts @@ -51,7 +51,7 @@ export const attioUpdateObjectTool: ToolConfig `https://api.attio.com/v2/objects/${params.object}`, + url: (params) => `https://api.attio.com/v2/objects/${params.object.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/update_record.ts b/apps/sim/tools/attio/update_record.ts index cf74d9a3b7..21d613f929 100644 --- a/apps/sim/tools/attio/update_record.ts +++ b/apps/sim/tools/attio/update_record.ts @@ -46,7 +46,7 @@ export const attioUpdateRecordTool: ToolConfig - `https://api.attio.com/v2/objects/${params.objectType}/records/${params.recordId}`, + `https://api.attio.com/v2/objects/${params.objectType.trim()}/records/${params.recordId.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/update_task.ts b/apps/sim/tools/attio/update_task.ts index 9b1e4e21e8..e55394b44c 100644 --- a/apps/sim/tools/attio/update_task.ts +++ b/apps/sim/tools/attio/update_task.ts @@ -56,7 +56,7 @@ export const attioUpdateTaskTool: ToolConfig `https://api.attio.com/v2/tasks/${params.taskId}`, + url: (params) => `https://api.attio.com/v2/tasks/${params.taskId.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/attio/update_webhook.ts b/apps/sim/tools/attio/update_webhook.ts index 662691c4fd..659bb8f964 100644 --- a/apps/sim/tools/attio/update_webhook.ts +++ b/apps/sim/tools/attio/update_webhook.ts @@ -34,41 +34,38 @@ export const attioUpdateWebhookTool: ToolConfig< }, targetUrl: { type: 'string', - required: true, + required: false, visibility: 'user-or-llm', description: 'HTTPS target URL for webhook delivery', }, subscriptions: { type: 'string', - required: true, + required: false, visibility: 'user-or-llm', description: 'JSON array of subscriptions, e.g. [{"event_type":"note.created"}]', }, }, request: { - url: (params) => `https://api.attio.com/v2/webhooks/${params.webhookId}`, + url: (params) => `https://api.attio.com/v2/webhooks/${params.webhookId.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, 'Content-Type': 'application/json', }), body: (params) => { - let subscriptions: unknown = [] + const data: Record = {} + if (params.targetUrl) data.target_url = params.targetUrl.trim() if (params.subscriptions) { try { - subscriptions = + data.subscriptions = typeof params.subscriptions === 'string' ? JSON.parse(params.subscriptions) : params.subscriptions } catch { - subscriptions = [] + data.subscriptions = [] } } - const data: Record = { - target_url: params.targetUrl, - subscriptions, - } return { data } }, }, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index b1b76f2224..a961819198 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -149,6 +149,7 @@ import { attioGetNoteTool, attioGetObjectTool, attioGetRecordTool, + attioGetTaskTool, attioGetThreadTool, attioGetWebhookTool, attioListListsTool, @@ -3970,6 +3971,7 @@ export const tools: Record = { attio_get_note: attioGetNoteTool, attio_get_object: attioGetObjectTool, attio_get_record: attioGetRecordTool, + attio_get_task: attioGetTaskTool, attio_get_thread: attioGetThreadTool, attio_get_webhook: attioGetWebhookTool, attio_list_lists: attioListListsTool, diff --git a/apps/sim/triggers/attio/index.ts b/apps/sim/triggers/attio/index.ts index f8c0e27f29..a6e77f3a63 100644 --- a/apps/sim/triggers/attio/index.ts +++ b/apps/sim/triggers/attio/index.ts @@ -2,9 +2,12 @@ export { attioCommentCreatedTrigger } from './comment_created' export { attioCommentDeletedTrigger } from './comment_deleted' export { attioCommentResolvedTrigger } from './comment_resolved' export { attioCommentUnresolvedTrigger } from './comment_unresolved' +export { attioListCreatedTrigger } from './list_created' +export { attioListDeletedTrigger } from './list_deleted' export { attioListEntryCreatedTrigger } from './list_entry_created' export { attioListEntryDeletedTrigger } from './list_entry_deleted' export { attioListEntryUpdatedTrigger } from './list_entry_updated' +export { attioListUpdatedTrigger } from './list_updated' export { attioNoteCreatedTrigger } from './note_created' export { attioNoteDeletedTrigger } from './note_deleted' export { attioNoteUpdatedTrigger } from './note_updated' @@ -16,3 +19,4 @@ export { attioTaskCreatedTrigger } from './task_created' export { attioTaskDeletedTrigger } from './task_deleted' export { attioTaskUpdatedTrigger } from './task_updated' export { attioWebhookTrigger } from './webhook' +export { attioWorkspaceMemberCreatedTrigger } from './workspace_member_created' diff --git a/apps/sim/triggers/attio/list_created.ts b/apps/sim/triggers/attio/list_created.ts new file mode 100644 index 0000000000..5f55567356 --- /dev/null +++ b/apps/sim/triggers/attio/list_created.ts @@ -0,0 +1,29 @@ +import { AttioIcon } from '@/components/icons' +import { buildAttioTriggerSubBlocks, buildListOutputs } from '@/triggers/attio/utils' +import type { TriggerConfig } from '@/triggers/types' + +/** + * Attio List Created Trigger + * + * Triggers when a list is created in Attio. + */ +export const attioListCreatedTrigger: TriggerConfig = { + id: 'attio_list_created', + name: 'Attio List Created', + provider: 'attio', + description: 'Trigger workflow when a list is created in Attio', + version: '1.0.0', + icon: AttioIcon, + + subBlocks: buildAttioTriggerSubBlocks('attio_list_created'), + + outputs: buildListOutputs(), + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Attio-Signature': 'hmac-sha256-signature', + }, + }, +} diff --git a/apps/sim/triggers/attio/list_deleted.ts b/apps/sim/triggers/attio/list_deleted.ts new file mode 100644 index 0000000000..9796cf257b --- /dev/null +++ b/apps/sim/triggers/attio/list_deleted.ts @@ -0,0 +1,29 @@ +import { AttioIcon } from '@/components/icons' +import { buildAttioTriggerSubBlocks, buildListOutputs } from '@/triggers/attio/utils' +import type { TriggerConfig } from '@/triggers/types' + +/** + * Attio List Deleted Trigger + * + * Triggers when a list is deleted in Attio. + */ +export const attioListDeletedTrigger: TriggerConfig = { + id: 'attio_list_deleted', + name: 'Attio List Deleted', + provider: 'attio', + description: 'Trigger workflow when a list is deleted in Attio', + version: '1.0.0', + icon: AttioIcon, + + subBlocks: buildAttioTriggerSubBlocks('attio_list_deleted'), + + outputs: buildListOutputs(), + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Attio-Signature': 'hmac-sha256-signature', + }, + }, +} diff --git a/apps/sim/triggers/attio/list_updated.ts b/apps/sim/triggers/attio/list_updated.ts new file mode 100644 index 0000000000..005cab5f6f --- /dev/null +++ b/apps/sim/triggers/attio/list_updated.ts @@ -0,0 +1,29 @@ +import { AttioIcon } from '@/components/icons' +import { buildAttioTriggerSubBlocks, buildListOutputs } from '@/triggers/attio/utils' +import type { TriggerConfig } from '@/triggers/types' + +/** + * Attio List Updated Trigger + * + * Triggers when a list is updated in Attio. + */ +export const attioListUpdatedTrigger: TriggerConfig = { + id: 'attio_list_updated', + name: 'Attio List Updated', + provider: 'attio', + description: 'Trigger workflow when a list is updated in Attio', + version: '1.0.0', + icon: AttioIcon, + + subBlocks: buildAttioTriggerSubBlocks('attio_list_updated'), + + outputs: buildListOutputs(), + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Attio-Signature': 'hmac-sha256-signature', + }, + }, +} diff --git a/apps/sim/triggers/attio/utils.ts b/apps/sim/triggers/attio/utils.ts index 11dfa89c30..9276f91ad4 100644 --- a/apps/sim/triggers/attio/utils.ts +++ b/apps/sim/triggers/attio/utils.ts @@ -19,6 +19,10 @@ export const attioTriggerOptions = [ { label: 'List Entry Created', id: 'attio_list_entry_created' }, { label: 'List Entry Updated', id: 'attio_list_entry_updated' }, { label: 'List Entry Deleted', id: 'attio_list_entry_deleted' }, + { label: 'List Created', id: 'attio_list_created' }, + { label: 'List Updated', id: 'attio_list_updated' }, + { label: 'List Deleted', id: 'attio_list_deleted' }, + { label: 'Workspace Member Created', id: 'attio_workspace_member_created' }, { label: 'Generic Webhook (All Events)', id: 'attio_webhook' }, ] @@ -235,6 +239,42 @@ export function buildCommentOutputs(): Record { } } +/** + * List event outputs. + */ +function buildListIdOutputs(): Record { + return { + workspaceId: { type: 'string', description: 'The workspace ID' }, + listId: { type: 'string', description: 'The list ID' }, + } +} + +/** List created/updated/deleted outputs. */ +export function buildListOutputs(): Record { + return { + ...buildBaseWebhookOutputs(), + ...buildListIdOutputs(), + } +} + +/** + * Workspace member event outputs. + */ +function buildWorkspaceMemberIdOutputs(): Record { + return { + workspaceId: { type: 'string', description: 'The workspace ID' }, + workspaceMemberId: { type: 'string', description: 'The workspace member ID' }, + } +} + +/** Workspace member outputs. */ +export function buildWorkspaceMemberOutputs(): Record { + return { + ...buildBaseWebhookOutputs(), + ...buildWorkspaceMemberIdOutputs(), + } +} + /** List entry created/deleted outputs. */ export function buildListEntryOutputs(): Record { return { @@ -276,7 +316,7 @@ export const TRIGGER_EVENT_MAP: Record = { attio_record_deleted: ['record.deleted'], attio_record_merged: ['record.merged'], attio_note_created: ['note.created'], - attio_note_updated: ['note.updated', 'note.content-updated'], + attio_note_updated: ['note.updated', 'note-content.updated'], attio_note_deleted: ['note.deleted'], attio_task_created: ['task.created'], attio_task_updated: ['task.updated'], @@ -288,6 +328,10 @@ export const TRIGGER_EVENT_MAP: Record = { attio_list_entry_created: ['list-entry.created'], attio_list_entry_updated: ['list-entry.updated'], attio_list_entry_deleted: ['list-entry.deleted'], + attio_list_created: ['list.created'], + attio_list_updated: ['list.updated'], + attio_list_deleted: ['list.deleted'], + attio_workspace_member_created: ['workspace-member.created'], } /** @@ -445,6 +489,35 @@ export function extractAttioListEntryUpdatedData( } } +/** + * Extracts formatted data from an Attio list event payload. + * Used for list.created, list.updated, list.deleted triggers. + */ +export function extractAttioListData(body: Record): Record { + const event = getAttioEvent(body) ?? {} + const id = (event.id as Record) ?? {} + return { + eventType: event.event_type ?? null, + workspaceId: id.workspace_id ?? null, + listId: id.list_id ?? null, + } +} + +/** + * Extracts formatted data from an Attio workspace-member.created event payload. + */ +export function extractAttioWorkspaceMemberData( + body: Record +): Record { + const event = getAttioEvent(body) ?? {} + const id = (event.id as Record) ?? {} + return { + eventType: event.event_type ?? null, + workspaceId: id.workspace_id ?? null, + workspaceMemberId: id.workspace_member_id ?? null, + } +} + /** * Extracts formatted data from a generic Attio webhook payload. * Passes through the first event with camelCase field mapping. diff --git a/apps/sim/triggers/attio/workspace_member_created.ts b/apps/sim/triggers/attio/workspace_member_created.ts new file mode 100644 index 0000000000..6d6d8145e6 --- /dev/null +++ b/apps/sim/triggers/attio/workspace_member_created.ts @@ -0,0 +1,29 @@ +import { AttioIcon } from '@/components/icons' +import { buildAttioTriggerSubBlocks, buildWorkspaceMemberOutputs } from '@/triggers/attio/utils' +import type { TriggerConfig } from '@/triggers/types' + +/** + * Attio Workspace Member Created Trigger + * + * Triggers when a new member is added to the Attio workspace. + */ +export const attioWorkspaceMemberCreatedTrigger: TriggerConfig = { + id: 'attio_workspace_member_created', + name: 'Attio Workspace Member Created', + provider: 'attio', + description: 'Trigger workflow when a new member is added to the Attio workspace', + version: '1.0.0', + icon: AttioIcon, + + subBlocks: buildAttioTriggerSubBlocks('attio_workspace_member_created'), + + outputs: buildWorkspaceMemberOutputs(), + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Attio-Signature': 'hmac-sha256-signature', + }, + }, +} diff --git a/apps/sim/triggers/registry.ts b/apps/sim/triggers/registry.ts index b0c61fe8c1..4390bfeeff 100644 --- a/apps/sim/triggers/registry.ts +++ b/apps/sim/triggers/registry.ts @@ -12,9 +12,12 @@ import { attioCommentDeletedTrigger, attioCommentResolvedTrigger, attioCommentUnresolvedTrigger, + attioListCreatedTrigger, + attioListDeletedTrigger, attioListEntryCreatedTrigger, attioListEntryDeletedTrigger, attioListEntryUpdatedTrigger, + attioListUpdatedTrigger, attioNoteCreatedTrigger, attioNoteDeletedTrigger, attioNoteUpdatedTrigger, @@ -26,6 +29,7 @@ import { attioTaskDeletedTrigger, attioTaskUpdatedTrigger, attioWebhookTrigger, + attioWorkspaceMemberCreatedTrigger, } from '@/triggers/attio' import { calcomBookingCancelledTrigger, @@ -200,6 +204,10 @@ export const TRIGGER_REGISTRY: TriggerRegistry = { attio_list_entry_created: attioListEntryCreatedTrigger, attio_list_entry_updated: attioListEntryUpdatedTrigger, attio_list_entry_deleted: attioListEntryDeletedTrigger, + attio_list_created: attioListCreatedTrigger, + attio_list_updated: attioListUpdatedTrigger, + attio_list_deleted: attioListDeletedTrigger, + attio_workspace_member_created: attioWorkspaceMemberCreatedTrigger, calendly_webhook: calendlyWebhookTrigger, calendly_invitee_created: calendlyInviteeCreatedTrigger, calendly_invitee_canceled: calendlyInviteeCanceledTrigger,