mirror of
https://github.com/simstudioai/sim.git
synced 2026-03-15 03:00:33 -04:00
Compare commits
4 Commits
v0.5.107
...
feat/tools
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
315e4509a3 | ||
|
|
5e3c43ff83 | ||
|
|
8db43b775c | ||
|
|
edf3c0dc06 |
@@ -3552,6 +3552,15 @@ export function TrelloIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function AttioIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60.9 50' fill='currentColor'>
|
||||
<path d='M60.3,34.8l-5.1-8.1c0,0,0,0,0,0L54.7,26c-0.8-1.2-2.1-1.9-3.5-1.9L43,24L42.5,25l-9.8,15.7l-0.5,0.9l4.1,6.6c0.8,1.2,2.1,1.9,3.5,1.9h11.5c1.4,0,2.8-0.7,3.5-1.9l0.4-0.6c0,0,0,0,0,0l5.1-8.2C61.1,37.9,61.1,36.2,60.3,34.8L60.3,34.8z M58.7,38.3l-5.1,8.2c0,0,0,0.1-0.1,0.1c-0.2,0.2-0.4,0.2-0.5,0.2c-0.1,0-0.4,0-0.6-0.3l-5.1-8.2c-0.1-0.1-0.1-0.2-0.2-0.3c0-0.1-0.1-0.2-0.1-0.3c-0.1-0.4-0.1-0.8,0-1.3c0.1-0.2,0.1-0.4,0.3-0.6l5.1-8.1c0,0,0,0,0,0c0.1-0.2,0.3-0.3,0.4-0.3c0.1,0,0.1,0,0.1,0c0,0,0,0,0.1,0c0.1,0,0.4,0,0.6,0.3l5.1,8.1C59.2,36.6,59.2,37.5,58.7,38.3L58.7,38.3z' />
|
||||
<path d='M45.2,15.1c0.8-1.3,0.8-3.1,0-4.4l-5.1-8.1l-0.4-0.7C38.9,0.7,37.6,0,36.2,0H24.7c-1.4,0-2.7,0.7-3.5,1.9L0.6,34.9C0.2,35.5,0,36.3,0,37c0,0.8,0.2,1.5,0.6,2.2l5.5,8.8C6.9,49.3,8.2,50,9.7,50h11.5c1.4,0,2.8-0.7,3.5-1.9l0.4-0.7c0,0,0,0,0,0c0,0,0,0,0,0l4.1-6.6l12.1-19.4L45.2,15.1L45.2,15.1z M44,13c0,0.4-0.1,0.8-0.4,1.2L23.5,46.4c-0.2,0.3-0.5,0.3-0.6,0.3c-0.1,0-0.4,0-0.6-0.3l-5.1-8.2c-0.5-0.7-0.5-1.7,0-2.4L37.4,3.6c0.2-0.3,0.5-0.3,0.6-0.3c0.1,0,0.4,0,0.6,0.3l5.1,8.1C43.9,12.1,44,12.5,44,13z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function AsanaIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ApolloIcon,
|
||||
ArxivIcon,
|
||||
AsanaIcon,
|
||||
AttioIcon,
|
||||
BrainIcon,
|
||||
BrowserUseIcon,
|
||||
CalComIcon,
|
||||
@@ -159,6 +160,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
apollo: ApolloIcon,
|
||||
arxiv: ArxivIcon,
|
||||
asana: AsanaIcon,
|
||||
attio: AttioIcon,
|
||||
browser_use: BrowserUseIcon,
|
||||
calcom: CalComIcon,
|
||||
calendly: CalendlyIcon,
|
||||
|
||||
1046
apps/docs/content/docs/en/tools/attio.mdx
Normal file
1046
apps/docs/content/docs/en/tools/attio.mdx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
"apollo",
|
||||
"arxiv",
|
||||
"asana",
|
||||
"attio",
|
||||
"browser_use",
|
||||
"calcom",
|
||||
"calendly",
|
||||
|
||||
@@ -301,6 +301,16 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
|
||||
'user-follow-modify': 'Follow and unfollow artists and users',
|
||||
'user-read-playback-position': 'View playback position in podcasts',
|
||||
'ugc-image-upload': 'Upload images to Spotify playlists',
|
||||
// Attio
|
||||
'record_permission:read-write': 'Read and write CRM records',
|
||||
'object_configuration:read-write': 'Read and manage object schemas',
|
||||
'list_configuration:read-write': 'Read and manage list configurations',
|
||||
'list_entry:read-write': 'Read and write list entries',
|
||||
'note:read-write': 'Read and write notes',
|
||||
'task:read-write': 'Read and write tasks',
|
||||
'comment:read-write': 'Read and write comments and threads',
|
||||
'user_management:read': 'View workspace members',
|
||||
'webhook:read-write': 'Manage webhooks',
|
||||
}
|
||||
|
||||
function getScopeDescription(scope: string): string {
|
||||
|
||||
1219
apps/sim/blocks/blocks/attio.ts
Normal file
1219
apps/sim/blocks/blocks/attio.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ import { ApifyBlock } from '@/blocks/blocks/apify'
|
||||
import { ApolloBlock } from '@/blocks/blocks/apollo'
|
||||
import { ArxivBlock } from '@/blocks/blocks/arxiv'
|
||||
import { AsanaBlock } from '@/blocks/blocks/asana'
|
||||
import { AttioBlock } from '@/blocks/blocks/attio'
|
||||
import { BrowserUseBlock } from '@/blocks/blocks/browser_use'
|
||||
import { CalComBlock } from '@/blocks/blocks/calcom'
|
||||
import { CalendlyBlock } from '@/blocks/blocks/calendly'
|
||||
@@ -187,6 +188,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
apollo: ApolloBlock,
|
||||
arxiv: ArxivBlock,
|
||||
asana: AsanaBlock,
|
||||
attio: AttioBlock,
|
||||
browser_use: BrowserUseBlock,
|
||||
calcom: CalComBlock,
|
||||
calendly: CalendlyBlock,
|
||||
|
||||
@@ -3552,6 +3552,15 @@ export function TrelloIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function AttioIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60.9 50' fill='currentColor'>
|
||||
<path d='M60.3,34.8l-5.1-8.1c0,0,0,0,0,0L54.7,26c-0.8-1.2-2.1-1.9-3.5-1.9L43,24L42.5,25l-9.8,15.7l-0.5,0.9l4.1,6.6c0.8,1.2,2.1,1.9,3.5,1.9h11.5c1.4,0,2.8-0.7,3.5-1.9l0.4-0.6c0,0,0,0,0,0l5.1-8.2C61.1,37.9,61.1,36.2,60.3,34.8L60.3,34.8z M58.7,38.3l-5.1,8.2c0,0,0,0.1-0.1,0.1c-0.2,0.2-0.4,0.2-0.5,0.2c-0.1,0-0.4,0-0.6-0.3l-5.1-8.2c-0.1-0.1-0.1-0.2-0.2-0.3c0-0.1-0.1-0.2-0.1-0.3c-0.1-0.4-0.1-0.8,0-1.3c0.1-0.2,0.1-0.4,0.3-0.6l5.1-8.1c0,0,0,0,0,0c0.1-0.2,0.3-0.3,0.4-0.3c0.1,0,0.1,0,0.1,0c0,0,0,0,0.1,0c0.1,0,0.4,0,0.6,0.3l5.1,8.1C59.2,36.6,59.2,37.5,58.7,38.3L58.7,38.3z' />
|
||||
<path d='M45.2,15.1c0.8-1.3,0.8-3.1,0-4.4l-5.1-8.1l-0.4-0.7C38.9,0.7,37.6,0,36.2,0H24.7c-1.4,0-2.7,0.7-3.5,1.9L0.6,34.9C0.2,35.5,0,36.3,0,37c0,0.8,0.2,1.5,0.6,2.2l5.5,8.8C6.9,49.3,8.2,50,9.7,50h11.5c1.4,0,2.8-0.7,3.5-1.9l0.4-0.7c0,0,0,0,0,0c0,0,0,0,0,0l4.1-6.6l12.1-19.4L45.2,15.1L45.2,15.1z M44,13c0,0.4-0.1,0.8-0.4,1.2L23.5,46.4c-0.2,0.3-0.5,0.3-0.6,0.3c-0.1,0-0.4,0-0.6-0.3l-5.1-8.2c-0.5-0.7-0.5-1.7,0-2.4L37.4,3.6c0.2-0.3,0.5-0.3,0.6-0.3c0.1,0,0.4,0,0.6,0.3l5.1,8.1C43.9,12.1,44,12.5,44,13z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function AsanaIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'>
|
||||
|
||||
@@ -503,6 +503,7 @@ export const auth = betterAuth({
|
||||
'zoom',
|
||||
'wordpress',
|
||||
'linear',
|
||||
'attio',
|
||||
'shopify',
|
||||
'trello',
|
||||
'calcom',
|
||||
@@ -2237,6 +2238,69 @@ export const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'attio',
|
||||
clientId: env.ATTIO_CLIENT_ID as string,
|
||||
clientSecret: env.ATTIO_CLIENT_SECRET as string,
|
||||
authorizationUrl: 'https://app.attio.com/authorize',
|
||||
tokenUrl: 'https://app.attio.com/oauth/token',
|
||||
scopes: [
|
||||
'record_permission:read-write',
|
||||
'object_configuration:read-write',
|
||||
'list_configuration:read-write',
|
||||
'list_entry:read-write',
|
||||
'note:read-write',
|
||||
'task:read-write',
|
||||
'comment:read-write',
|
||||
'user_management:read',
|
||||
'webhook:read-write',
|
||||
],
|
||||
responseType: 'code',
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/attio`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://api.attio.com/v2/workspace_members', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('Attio API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: errorText,
|
||||
})
|
||||
throw new Error(`Attio API error: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const { data } = await response.json()
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
throw new Error('No workspace members found in Attio response')
|
||||
}
|
||||
|
||||
const member = data[0]
|
||||
|
||||
return {
|
||||
id: `${member.id.workspace_member_id}-${crypto.randomUUID()}`,
|
||||
email: member.email_address,
|
||||
name:
|
||||
`${member.first_name ?? ''} ${member.last_name ?? ''}`.trim() ||
|
||||
member.email_address,
|
||||
emailVerified: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
image: member.avatar_url || undefined,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Attio getUserInfo:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'dropbox',
|
||||
clientId: env.DROPBOX_CLIENT_ID as string,
|
||||
|
||||
@@ -281,6 +281,8 @@ export const env = createEnv({
|
||||
SPOTIFY_CLIENT_ID: z.string().optional(), // Spotify OAuth client ID
|
||||
SPOTIFY_CLIENT_SECRET: z.string().optional(), // Spotify OAuth client secret
|
||||
CALCOM_CLIENT_ID: z.string().optional(), // Cal.com OAuth client ID
|
||||
ATTIO_CLIENT_ID: z.string().optional(), // Attio OAuth client ID
|
||||
ATTIO_CLIENT_SECRET: z.string().optional(), // Attio OAuth client secret
|
||||
|
||||
// E2B Remote Code Execution
|
||||
E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import {
|
||||
AirtableIcon,
|
||||
AsanaIcon,
|
||||
AttioIcon,
|
||||
CalComIcon,
|
||||
ConfluenceIcon,
|
||||
DropboxIcon,
|
||||
@@ -629,6 +630,31 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
},
|
||||
defaultService: 'asana',
|
||||
},
|
||||
attio: {
|
||||
name: 'Attio',
|
||||
icon: AttioIcon,
|
||||
services: {
|
||||
attio: {
|
||||
name: 'Attio',
|
||||
description: 'Manage records, notes, tasks, lists, comments, and more in Attio CRM.',
|
||||
providerId: 'attio',
|
||||
icon: AttioIcon,
|
||||
baseProviderIcon: AttioIcon,
|
||||
scopes: [
|
||||
'record_permission:read-write',
|
||||
'object_configuration:read-write',
|
||||
'list_configuration:read-write',
|
||||
'list_entry:read-write',
|
||||
'note:read-write',
|
||||
'task:read-write',
|
||||
'comment:read-write',
|
||||
'user_management:read',
|
||||
'webhook:read-write',
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultService: 'attio',
|
||||
},
|
||||
calcom: {
|
||||
name: 'Cal.com',
|
||||
icon: CalComIcon,
|
||||
@@ -966,6 +992,18 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
|
||||
supportsRefreshTokenRotation: true,
|
||||
}
|
||||
}
|
||||
case 'attio': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.ATTIO_CLIENT_ID,
|
||||
env.ATTIO_CLIENT_SECRET
|
||||
)
|
||||
return {
|
||||
tokenEndpoint: 'https://app.attio.com/oauth/token',
|
||||
clientId,
|
||||
clientSecret,
|
||||
useBasicAuth: false,
|
||||
}
|
||||
}
|
||||
case 'dropbox': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.DROPBOX_CLIENT_ID,
|
||||
|
||||
@@ -34,6 +34,7 @@ export type OAuthProvider =
|
||||
| 'wealthbox'
|
||||
| 'webflow'
|
||||
| 'asana'
|
||||
| 'attio'
|
||||
| 'pipedrive'
|
||||
| 'hubspot'
|
||||
| 'salesforce'
|
||||
@@ -76,6 +77,7 @@ export type OAuthService =
|
||||
| 'webflow'
|
||||
| 'trello'
|
||||
| 'asana'
|
||||
| 'attio'
|
||||
| 'pipedrive'
|
||||
| 'hubspot'
|
||||
| 'salesforce'
|
||||
|
||||
@@ -946,6 +946,28 @@ export async function queueWebhookExecution(
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'attio') {
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
const triggerId = providerConfig.triggerId as string | undefined
|
||||
|
||||
if (triggerId && triggerId !== 'attio_webhook') {
|
||||
const { isAttioPayloadMatch } = await import('@/triggers/attio/utils')
|
||||
if (!isAttioPayloadMatch(triggerId, body)) {
|
||||
const eventType = body?.event_type as string | undefined
|
||||
logger.debug(
|
||||
`[${options.requestId}] Attio event mismatch for trigger ${triggerId}. Event: ${eventType}. Skipping execution.`,
|
||||
{
|
||||
webhookId: foundWebhook.id,
|
||||
workflowId: foundWorkflow.id,
|
||||
triggerId,
|
||||
receivedEvent: eventType,
|
||||
}
|
||||
)
|
||||
return NextResponse.json({ status: 'skipped', reason: 'event_type_mismatch' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'hubspot') {
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
const triggerId = providerConfig.triggerId as string | undefined
|
||||
|
||||
95
apps/sim/tools/attio/assert_record.ts
Normal file
95
apps/sim/tools/attio/assert_record.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioAssertRecordParams, AttioAssertRecordResponse } from './types'
|
||||
import { RECORD_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioAssertRecord')
|
||||
|
||||
export const attioAssertRecordTool: ToolConfig<AttioAssertRecordParams, AttioAssertRecordResponse> =
|
||||
{
|
||||
id: 'attio_assert_record',
|
||||
name: 'Attio Assert Record',
|
||||
description:
|
||||
'Upsert a record in Attio — creates it if no match is found, updates it if a match exists',
|
||||
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',
|
||||
},
|
||||
objectType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The object type slug (e.g. people, companies)',
|
||||
},
|
||||
matchingAttribute: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The attribute slug to match on for upsert (e.g. email_addresses for people, domains for companies)',
|
||||
},
|
||||
values: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON object of attribute values (e.g. {"email_addresses":[{"email_address":"test@example.com"}]})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.attio.com/v2/objects/${params.objectType}/records?matching_attribute=${params.matchingAttribute}`,
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let values: Record<string, unknown> = {}
|
||||
try {
|
||||
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
|
||||
} catch {
|
||||
values = {}
|
||||
}
|
||||
return { data: { values } }
|
||||
},
|
||||
},
|
||||
|
||||
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 assert record')
|
||||
}
|
||||
const record = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
record,
|
||||
recordId: record.id?.record_id ?? null,
|
||||
webUrl: record.web_url ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
record: {
|
||||
type: 'object',
|
||||
description: 'The upserted record',
|
||||
properties: RECORD_OUTPUT_PROPERTIES,
|
||||
},
|
||||
recordId: { type: 'string', description: 'The record ID' },
|
||||
webUrl: { type: 'string', description: 'URL to view the record in Attio' },
|
||||
},
|
||||
}
|
||||
137
apps/sim/tools/attio/create_comment.ts
Normal file
137
apps/sim/tools/attio/create_comment.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioCreateCommentParams, AttioCreateCommentResponse } from './types'
|
||||
import { COMMENT_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioCreateComment')
|
||||
|
||||
export const attioCreateCommentTool: ToolConfig<
|
||||
AttioCreateCommentParams,
|
||||
AttioCreateCommentResponse
|
||||
> = {
|
||||
id: 'attio_create_comment',
|
||||
name: 'Attio Create Comment',
|
||||
description: 'Create a comment on a list entry in 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',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The comment content',
|
||||
},
|
||||
format: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Content format: plaintext or markdown (default plaintext)',
|
||||
},
|
||||
authorType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Author type (e.g. workspace-member)',
|
||||
},
|
||||
authorId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Author workspace member ID',
|
||||
},
|
||||
list: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The list ID or slug the entry belongs to',
|
||||
},
|
||||
entryId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The entry ID to comment on',
|
||||
},
|
||||
threadId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Thread ID to reply to (omit to start a new thread)',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Backdate the comment (ISO 8601 format)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.attio.com/v2/comments',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const data: Record<string, unknown> = {
|
||||
format: params.format || 'plaintext',
|
||||
content: params.content,
|
||||
author: {
|
||||
type: params.authorType,
|
||||
id: params.authorId,
|
||||
},
|
||||
entry: {
|
||||
list: params.list,
|
||||
entry_id: params.entryId,
|
||||
},
|
||||
}
|
||||
if (params.threadId) data.thread_id = params.threadId
|
||||
if (params.createdAt) data.created_at = params.createdAt
|
||||
return { data }
|
||||
},
|
||||
},
|
||||
|
||||
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 create comment')
|
||||
}
|
||||
const c = data.data
|
||||
const author = c.author as { type?: string; id?: string } | undefined
|
||||
const entry = c.entry as { list_id?: string; entry_id?: string } | undefined
|
||||
const record = c.record as { object_id?: string; record_id?: string } | undefined
|
||||
const resolvedBy = c.resolved_by as { type?: string; id?: string } | undefined
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
commentId: c.id?.comment_id ?? null,
|
||||
threadId: c.thread_id ?? null,
|
||||
contentPlaintext: c.content_plaintext ?? null,
|
||||
author: author ? { type: author.type ?? null, id: author.id ?? null } : null,
|
||||
entry: entry ? { listId: entry.list_id ?? null, entryId: entry.entry_id ?? null } : null,
|
||||
record: record
|
||||
? { objectId: record.object_id ?? null, recordId: record.record_id ?? null }
|
||||
: null,
|
||||
resolvedAt: c.resolved_at ?? null,
|
||||
resolvedBy: resolvedBy
|
||||
? { type: resolvedBy.type ?? null, id: resolvedBy.id ?? null }
|
||||
: null,
|
||||
createdAt: c.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: COMMENT_OUTPUT_PROPERTIES,
|
||||
}
|
||||
114
apps/sim/tools/attio/create_list.ts
Normal file
114
apps/sim/tools/attio/create_list.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioCreateListParams, AttioCreateListResponse } from './types'
|
||||
import { LIST_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioCreateList')
|
||||
|
||||
export const attioCreateListTool: ToolConfig<AttioCreateListParams, AttioCreateListResponse> = {
|
||||
id: 'attio_create_list',
|
||||
name: 'Attio Create List',
|
||||
description: 'Create a new list in 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',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The list name',
|
||||
},
|
||||
apiSlug: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The API slug for the list (auto-generated from name if omitted)',
|
||||
},
|
||||
parentObject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The parent object slug (e.g. people, companies)',
|
||||
},
|
||||
workspaceAccess: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Workspace-level access: full-access, read-and-write, or read-only (omit for private)',
|
||||
},
|
||||
workspaceMemberAccess: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of member access entries, e.g. [{"workspace_member_id":"...","level":"read-and-write"}]',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.attio.com/v2/lists',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const data: Record<string, unknown> = {
|
||||
name: params.name,
|
||||
parent_object: params.parentObject,
|
||||
}
|
||||
if (params.apiSlug) data.api_slug = params.apiSlug
|
||||
if (params.workspaceAccess) data.workspace_access = params.workspaceAccess
|
||||
if (params.workspaceMemberAccess) {
|
||||
try {
|
||||
data.workspace_member_access =
|
||||
typeof params.workspaceMemberAccess === 'string'
|
||||
? JSON.parse(params.workspaceMemberAccess)
|
||||
: params.workspaceMemberAccess
|
||||
} catch {
|
||||
data.workspace_member_access = params.workspaceMemberAccess
|
||||
}
|
||||
}
|
||||
return { data }
|
||||
},
|
||||
},
|
||||
|
||||
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 create list')
|
||||
}
|
||||
const list = data.data
|
||||
const actor = list.created_by_actor as { type?: string; id?: string } | undefined
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
listId: list.id?.list_id ?? null,
|
||||
apiSlug: list.api_slug ?? null,
|
||||
name: list.name ?? null,
|
||||
parentObject: Array.isArray(list.parent_object)
|
||||
? (list.parent_object[0] ?? null)
|
||||
: (list.parent_object ?? null),
|
||||
workspaceAccess: list.workspace_access ?? null,
|
||||
workspaceMemberAccess: list.workspace_member_access ?? null,
|
||||
createdByActor: actor ? { type: actor.type ?? null, id: actor.id ?? null } : null,
|
||||
createdAt: list.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: LIST_OUTPUT_PROPERTIES,
|
||||
}
|
||||
102
apps/sim/tools/attio/create_list_entry.ts
Normal file
102
apps/sim/tools/attio/create_list_entry.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioCreateListEntryParams, AttioCreateListEntryResponse } from './types'
|
||||
import { LIST_ENTRY_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioCreateListEntry')
|
||||
|
||||
export const attioCreateListEntryTool: ToolConfig<
|
||||
AttioCreateListEntryParams,
|
||||
AttioCreateListEntryResponse
|
||||
> = {
|
||||
id: 'attio_create_list_entry',
|
||||
name: 'Attio Create List Entry',
|
||||
description: 'Add a record to an Attio list as a new entry',
|
||||
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',
|
||||
},
|
||||
list: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The list ID or slug',
|
||||
},
|
||||
parentRecordId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The record ID to add to the list',
|
||||
},
|
||||
parentObject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The object type slug of the record (e.g. people, companies)',
|
||||
},
|
||||
entryValues: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object of entry attribute values',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/lists/${params.list}/entries`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const data: Record<string, unknown> = {
|
||||
parent_record_id: params.parentRecordId,
|
||||
parent_object: params.parentObject,
|
||||
}
|
||||
if (params.entryValues) {
|
||||
try {
|
||||
data.entry_values =
|
||||
typeof params.entryValues === 'string'
|
||||
? JSON.parse(params.entryValues)
|
||||
: params.entryValues
|
||||
} catch {
|
||||
data.entry_values = {}
|
||||
}
|
||||
}
|
||||
return { data }
|
||||
},
|
||||
},
|
||||
|
||||
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 create list entry')
|
||||
}
|
||||
const entry = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
entryId: entry.id?.entry_id ?? null,
|
||||
listId: entry.id?.list_id ?? null,
|
||||
parentRecordId: entry.parent_record_id ?? null,
|
||||
parentObject: entry.parent_object ?? null,
|
||||
createdAt: entry.created_at ?? null,
|
||||
entryValues: entry.entry_values ?? {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: LIST_ENTRY_OUTPUT_PROPERTIES,
|
||||
}
|
||||
116
apps/sim/tools/attio/create_note.ts
Normal file
116
apps/sim/tools/attio/create_note.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioCreateNoteParams, AttioCreateNoteResponse } from './types'
|
||||
import { NOTE_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioCreateNote')
|
||||
|
||||
export const attioCreateNoteTool: ToolConfig<AttioCreateNoteParams, AttioCreateNoteResponse> = {
|
||||
id: 'attio_create_note',
|
||||
name: 'Attio Create Note',
|
||||
description: 'Create a note on a record in 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',
|
||||
},
|
||||
parentObject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The parent object type slug (e.g. people, companies)',
|
||||
},
|
||||
parentRecordId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The parent record ID to attach the note to',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The note title',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The note content',
|
||||
},
|
||||
format: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Content format: plaintext or markdown (default plaintext)',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Backdate the note creation time (ISO 8601 format)',
|
||||
},
|
||||
meetingId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Associate the note with a meeting ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.attio.com/v2/notes',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
parent_object: params.parentObject,
|
||||
parent_record_id: params.parentRecordId,
|
||||
title: params.title,
|
||||
format: params.format || 'plaintext',
|
||||
content: params.content,
|
||||
}
|
||||
if (params.createdAt) body.created_at = params.createdAt
|
||||
if (params.meetingId !== undefined) body.meeting_id = params.meetingId || null
|
||||
return { data: body }
|
||||
},
|
||||
},
|
||||
|
||||
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 create note')
|
||||
}
|
||||
const note = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
noteId: note.id?.note_id ?? null,
|
||||
parentObject: note.parent_object ?? null,
|
||||
parentRecordId: note.parent_record_id ?? null,
|
||||
title: note.title ?? null,
|
||||
contentPlaintext: note.content_plaintext ?? null,
|
||||
contentMarkdown: note.content_markdown ?? null,
|
||||
meetingId: note.meeting_id ?? null,
|
||||
tags: note.tags ?? [],
|
||||
createdByActor: note.created_by_actor ?? null,
|
||||
createdAt: note.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: NOTE_OUTPUT_PROPERTIES,
|
||||
}
|
||||
83
apps/sim/tools/attio/create_object.ts
Normal file
83
apps/sim/tools/attio/create_object.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioCreateObjectParams, AttioCreateObjectResponse } from './types'
|
||||
import { OBJECT_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioCreateObject')
|
||||
|
||||
export const attioCreateObjectTool: ToolConfig<AttioCreateObjectParams, AttioCreateObjectResponse> =
|
||||
{
|
||||
id: 'attio_create_object',
|
||||
name: 'Attio Create Object',
|
||||
description: 'Create a custom object in 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',
|
||||
},
|
||||
apiSlug: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The API slug for the object (e.g. projects)',
|
||||
},
|
||||
singularNoun: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Singular display name (e.g. Project)',
|
||||
},
|
||||
pluralNoun: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Plural display name (e.g. Projects)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.attio.com/v2/objects',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
data: {
|
||||
api_slug: params.apiSlug,
|
||||
singular_noun: params.singularNoun,
|
||||
plural_noun: params.pluralNoun,
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
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 create object')
|
||||
}
|
||||
const obj = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
objectId: obj.id?.object_id ?? null,
|
||||
apiSlug: obj.api_slug ?? null,
|
||||
singularNoun: obj.singular_noun ?? null,
|
||||
pluralNoun: obj.plural_noun ?? null,
|
||||
createdAt: obj.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: OBJECT_OUTPUT_PROPERTIES,
|
||||
}
|
||||
81
apps/sim/tools/attio/create_record.ts
Normal file
81
apps/sim/tools/attio/create_record.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioCreateRecordParams, AttioCreateRecordResponse } from './types'
|
||||
import { RECORD_OBJECT_OUTPUT } from './types'
|
||||
|
||||
const logger = createLogger('AttioCreateRecord')
|
||||
|
||||
export const attioCreateRecordTool: ToolConfig<AttioCreateRecordParams, AttioCreateRecordResponse> =
|
||||
{
|
||||
id: 'attio_create_record',
|
||||
name: 'Attio Create Record',
|
||||
description: 'Create a new record in Attio for a given object type',
|
||||
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',
|
||||
},
|
||||
objectType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The object type slug (e.g. people, companies)',
|
||||
},
|
||||
values: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object of attribute values to set on the record',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/objects/${params.objectType}/records`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let values: Record<string, unknown>
|
||||
try {
|
||||
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
|
||||
} catch {
|
||||
values = {}
|
||||
}
|
||||
return { data: { values } }
|
||||
},
|
||||
},
|
||||
|
||||
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 create record')
|
||||
}
|
||||
const record = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
record,
|
||||
recordId: record.id?.record_id ?? null,
|
||||
webUrl: record.web_url ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
record: RECORD_OBJECT_OUTPUT,
|
||||
recordId: { type: 'string', description: 'The ID of the created record' },
|
||||
webUrl: { type: 'string', description: 'URL to view the record in Attio' },
|
||||
},
|
||||
}
|
||||
136
apps/sim/tools/attio/create_task.ts
Normal file
136
apps/sim/tools/attio/create_task.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioCreateTaskParams, AttioCreateTaskResponse } from './types'
|
||||
import { TASK_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioCreateTask')
|
||||
|
||||
export const attioCreateTaskTool: ToolConfig<AttioCreateTaskParams, AttioCreateTaskResponse> = {
|
||||
id: 'attio_create_task',
|
||||
name: 'Attio Create Task',
|
||||
description: 'Create a task in 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',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The task content (max 2000 characters)',
|
||||
},
|
||||
deadlineAt: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Deadline in ISO 8601 format (e.g. 2024-12-01T15:00:00.000Z)',
|
||||
},
|
||||
isCompleted: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether the task is completed (default false)',
|
||||
},
|
||||
linkedRecords: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of linked records (e.g. [{"target_object":"people","target_record_id":"..."}])',
|
||||
},
|
||||
assignees: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of assignees (e.g. [{"referenced_actor_type":"workspace-member","referenced_actor_id":"..."}])',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.attio.com/v2/tasks',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let linkedRecords: unknown[] = []
|
||||
let assignees: unknown[] = []
|
||||
try {
|
||||
if (params.linkedRecords) {
|
||||
linkedRecords =
|
||||
typeof params.linkedRecords === 'string'
|
||||
? JSON.parse(params.linkedRecords)
|
||||
: params.linkedRecords
|
||||
}
|
||||
} catch {
|
||||
linkedRecords = []
|
||||
}
|
||||
try {
|
||||
if (params.assignees) {
|
||||
assignees =
|
||||
typeof params.assignees === 'string' ? JSON.parse(params.assignees) : params.assignees
|
||||
}
|
||||
} catch {
|
||||
assignees = []
|
||||
}
|
||||
return {
|
||||
data: {
|
||||
content: params.content,
|
||||
format: 'plaintext',
|
||||
deadline_at: params.deadlineAt || null,
|
||||
is_completed: params.isCompleted ?? false,
|
||||
linked_records: linkedRecords,
|
||||
assignees,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
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 create 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,
|
||||
}
|
||||
104
apps/sim/tools/attio/create_webhook.ts
Normal file
104
apps/sim/tools/attio/create_webhook.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioCreateWebhookParams, AttioCreateWebhookResponse } from './types'
|
||||
import { WEBHOOK_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioCreateWebhook')
|
||||
|
||||
export const attioCreateWebhookTool: ToolConfig<
|
||||
AttioCreateWebhookParams,
|
||||
AttioCreateWebhookResponse
|
||||
> = {
|
||||
id: 'attio_create_webhook',
|
||||
name: 'Attio Create Webhook',
|
||||
description: 'Create a webhook in Attio to receive event notifications',
|
||||
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',
|
||||
},
|
||||
targetUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The HTTPS URL to receive webhook events',
|
||||
},
|
||||
subscriptions: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of subscriptions (e.g. [{"event_type":"record.created","filter":{"object_id":"..."}}])',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.attio.com/v2/webhooks',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let subscriptions: unknown[] = []
|
||||
try {
|
||||
subscriptions =
|
||||
typeof params.subscriptions === 'string'
|
||||
? JSON.parse(params.subscriptions)
|
||||
: params.subscriptions
|
||||
} catch {
|
||||
subscriptions = []
|
||||
}
|
||||
return {
|
||||
data: {
|
||||
target_url: params.targetUrl,
|
||||
subscriptions,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
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 create webhook')
|
||||
}
|
||||
const w = data.data
|
||||
const subs =
|
||||
(w.subscriptions as Array<{ event_type?: string; filter?: unknown }>)?.map(
|
||||
(s: { event_type?: string; filter?: unknown }) => ({
|
||||
eventType: s.event_type ?? null,
|
||||
filter: s.filter ?? null,
|
||||
})
|
||||
) ?? []
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
webhookId: w.id?.webhook_id ?? null,
|
||||
targetUrl: w.target_url ?? null,
|
||||
subscriptions: subs,
|
||||
status: w.status ?? null,
|
||||
secret: w.secret ?? null,
|
||||
createdAt: w.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...WEBHOOK_OUTPUT_PROPERTIES,
|
||||
secret: {
|
||||
type: 'string',
|
||||
description: 'The webhook signing secret (only returned on creation)',
|
||||
},
|
||||
},
|
||||
}
|
||||
61
apps/sim/tools/attio/delete_comment.ts
Normal file
61
apps/sim/tools/attio/delete_comment.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioDeleteCommentParams, AttioDeleteCommentResponse } from './types'
|
||||
|
||||
const logger = createLogger('AttioDeleteComment')
|
||||
|
||||
export const attioDeleteCommentTool: ToolConfig<
|
||||
AttioDeleteCommentParams,
|
||||
AttioDeleteCommentResponse
|
||||
> = {
|
||||
id: 'attio_delete_comment',
|
||||
name: 'Attio Delete Comment',
|
||||
description: 'Delete a comment in Attio (if head of thread, deletes entire thread)',
|
||||
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',
|
||||
},
|
||||
commentId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The comment ID to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/comments/${params.commentId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
logger.error('Attio API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to delete comment')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether the comment was deleted' },
|
||||
},
|
||||
}
|
||||
67
apps/sim/tools/attio/delete_list_entry.ts
Normal file
67
apps/sim/tools/attio/delete_list_entry.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioDeleteListEntryParams, AttioDeleteListEntryResponse } from './types'
|
||||
|
||||
const logger = createLogger('AttioDeleteListEntry')
|
||||
|
||||
export const attioDeleteListEntryTool: ToolConfig<
|
||||
AttioDeleteListEntryParams,
|
||||
AttioDeleteListEntryResponse
|
||||
> = {
|
||||
id: 'attio_delete_list_entry',
|
||||
name: 'Attio Delete List Entry',
|
||||
description: 'Remove an entry from an Attio list',
|
||||
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',
|
||||
},
|
||||
list: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The list ID or slug',
|
||||
},
|
||||
entryId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The entry ID to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/lists/${params.list}/entries/${params.entryId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
logger.error('Attio API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to delete list entry')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether the entry was deleted' },
|
||||
},
|
||||
}
|
||||
58
apps/sim/tools/attio/delete_note.ts
Normal file
58
apps/sim/tools/attio/delete_note.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioDeleteNoteParams, AttioDeleteNoteResponse } from './types'
|
||||
|
||||
const logger = createLogger('AttioDeleteNote')
|
||||
|
||||
export const attioDeleteNoteTool: ToolConfig<AttioDeleteNoteParams, AttioDeleteNoteResponse> = {
|
||||
id: 'attio_delete_note',
|
||||
name: 'Attio Delete Note',
|
||||
description: 'Delete a note 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',
|
||||
},
|
||||
noteId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the note to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/notes/${params.noteId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
logger.error('Attio API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to delete note')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether the note was deleted' },
|
||||
},
|
||||
}
|
||||
66
apps/sim/tools/attio/delete_record.ts
Normal file
66
apps/sim/tools/attio/delete_record.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioDeleteRecordParams, AttioDeleteRecordResponse } from './types'
|
||||
|
||||
const logger = createLogger('AttioDeleteRecord')
|
||||
|
||||
export const attioDeleteRecordTool: ToolConfig<AttioDeleteRecordParams, AttioDeleteRecordResponse> =
|
||||
{
|
||||
id: 'attio_delete_record',
|
||||
name: 'Attio Delete Record',
|
||||
description: 'Delete a record 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',
|
||||
},
|
||||
objectType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The object type slug (e.g. people, companies)',
|
||||
},
|
||||
recordId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the record to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.attio.com/v2/objects/${params.objectType}/records/${params.recordId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
logger.error('Attio API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to delete record')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether the record was deleted' },
|
||||
},
|
||||
}
|
||||
58
apps/sim/tools/attio/delete_task.ts
Normal file
58
apps/sim/tools/attio/delete_task.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioDeleteTaskParams, AttioDeleteTaskResponse } from './types'
|
||||
|
||||
const logger = createLogger('AttioDeleteTask')
|
||||
|
||||
export const attioDeleteTaskTool: ToolConfig<AttioDeleteTaskParams, AttioDeleteTaskResponse> = {
|
||||
id: 'attio_delete_task',
|
||||
name: 'Attio Delete Task',
|
||||
description: 'Delete a task 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 delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/tasks/${params.taskId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
logger.error('Attio API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to delete task')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether the task was deleted' },
|
||||
},
|
||||
}
|
||||
61
apps/sim/tools/attio/delete_webhook.ts
Normal file
61
apps/sim/tools/attio/delete_webhook.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioDeleteWebhookParams, AttioDeleteWebhookResponse } from './types'
|
||||
|
||||
const logger = createLogger('AttioDeleteWebhook')
|
||||
|
||||
export const attioDeleteWebhookTool: ToolConfig<
|
||||
AttioDeleteWebhookParams,
|
||||
AttioDeleteWebhookResponse
|
||||
> = {
|
||||
id: 'attio_delete_webhook',
|
||||
name: 'Attio Delete Webhook',
|
||||
description: 'Delete a webhook 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',
|
||||
},
|
||||
webhookId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The webhook ID to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/webhooks/${params.webhookId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
logger.error('Attio API request failed', { data, status: response.status })
|
||||
throw new Error(data.message || 'Failed to delete webhook')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether the webhook was deleted' },
|
||||
},
|
||||
}
|
||||
74
apps/sim/tools/attio/get_comment.ts
Normal file
74
apps/sim/tools/attio/get_comment.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioGetCommentParams, AttioGetCommentResponse } from './types'
|
||||
import { COMMENT_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioGetComment')
|
||||
|
||||
export const attioGetCommentTool: ToolConfig<AttioGetCommentParams, AttioGetCommentResponse> = {
|
||||
id: 'attio_get_comment',
|
||||
name: 'Attio Get Comment',
|
||||
description: 'Get a single comment 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',
|
||||
},
|
||||
commentId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The comment ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/comments/${params.commentId}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 comment')
|
||||
}
|
||||
const c = data.data
|
||||
const author = c.author as { type?: string; id?: string } | undefined
|
||||
const entry = c.entry as { list_id?: string; entry_id?: string } | undefined
|
||||
const record = c.record as { object_id?: string; record_id?: string } | undefined
|
||||
const resolvedBy = c.resolved_by as { type?: string; id?: string } | undefined
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
commentId: c.id?.comment_id ?? null,
|
||||
threadId: c.thread_id ?? null,
|
||||
contentPlaintext: c.content_plaintext ?? null,
|
||||
author: author ? { type: author.type ?? null, id: author.id ?? null } : null,
|
||||
entry: entry ? { listId: entry.list_id ?? null, entryId: entry.entry_id ?? null } : null,
|
||||
record: record
|
||||
? { objectId: record.object_id ?? null, recordId: record.record_id ?? null }
|
||||
: null,
|
||||
resolvedAt: c.resolved_at ?? null,
|
||||
resolvedBy: resolvedBy
|
||||
? { type: resolvedBy.type ?? null, id: resolvedBy.id ?? null }
|
||||
: null,
|
||||
createdAt: c.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: COMMENT_OUTPUT_PROPERTIES,
|
||||
}
|
||||
68
apps/sim/tools/attio/get_list.ts
Normal file
68
apps/sim/tools/attio/get_list.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioGetListParams, AttioGetListResponse } from './types'
|
||||
import { LIST_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioGetList')
|
||||
|
||||
export const attioGetListTool: ToolConfig<AttioGetListParams, AttioGetListResponse> = {
|
||||
id: 'attio_get_list',
|
||||
name: 'Attio Get List',
|
||||
description: 'Get a single list by ID or slug',
|
||||
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',
|
||||
},
|
||||
list: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The list ID or slug',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/lists/${params.list}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 list')
|
||||
}
|
||||
const list = data.data
|
||||
const actor = list.created_by_actor as { type?: string; id?: string } | undefined
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
listId: list.id?.list_id ?? null,
|
||||
apiSlug: list.api_slug ?? null,
|
||||
name: list.name ?? null,
|
||||
parentObject: Array.isArray(list.parent_object)
|
||||
? (list.parent_object[0] ?? null)
|
||||
: (list.parent_object ?? null),
|
||||
workspaceAccess: list.workspace_access ?? null,
|
||||
workspaceMemberAccess: list.workspace_member_access ?? null,
|
||||
createdByActor: actor ? { type: actor.type ?? null, id: actor.id ?? null } : null,
|
||||
createdAt: list.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: LIST_OUTPUT_PROPERTIES,
|
||||
}
|
||||
70
apps/sim/tools/attio/get_list_entry.ts
Normal file
70
apps/sim/tools/attio/get_list_entry.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioGetListEntryParams, AttioGetListEntryResponse } from './types'
|
||||
import { LIST_ENTRY_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioGetListEntry')
|
||||
|
||||
export const attioGetListEntryTool: ToolConfig<AttioGetListEntryParams, AttioGetListEntryResponse> =
|
||||
{
|
||||
id: 'attio_get_list_entry',
|
||||
name: 'Attio Get List Entry',
|
||||
description: 'Get a single list entry by ID',
|
||||
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',
|
||||
},
|
||||
list: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The list ID or slug',
|
||||
},
|
||||
entryId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The entry ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/lists/${params.list}/entries/${params.entryId}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 list entry')
|
||||
}
|
||||
const entry = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
entryId: entry.id?.entry_id ?? null,
|
||||
listId: entry.id?.list_id ?? null,
|
||||
parentRecordId: entry.parent_record_id ?? null,
|
||||
parentObject: entry.parent_object ?? null,
|
||||
createdAt: entry.created_at ?? null,
|
||||
entryValues: entry.entry_values ?? {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: LIST_ENTRY_OUTPUT_PROPERTIES,
|
||||
}
|
||||
64
apps/sim/tools/attio/get_member.ts
Normal file
64
apps/sim/tools/attio/get_member.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioGetMemberParams, AttioGetMemberResponse } from './types'
|
||||
import { MEMBER_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioGetMember')
|
||||
|
||||
export const attioGetMemberTool: ToolConfig<AttioGetMemberParams, AttioGetMemberResponse> = {
|
||||
id: 'attio_get_member',
|
||||
name: 'Attio Get Member',
|
||||
description: 'Get a single workspace member by ID',
|
||||
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',
|
||||
},
|
||||
memberId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The workspace member ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/workspace_members/${params.memberId}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 workspace member')
|
||||
}
|
||||
const m = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
memberId: m.id?.workspace_member_id ?? null,
|
||||
firstName: m.first_name ?? null,
|
||||
lastName: m.last_name ?? null,
|
||||
avatarUrl: m.avatar_url ?? null,
|
||||
emailAddress: m.email_address ?? null,
|
||||
accessLevel: m.access_level ?? null,
|
||||
createdAt: m.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: MEMBER_OUTPUT_PROPERTIES,
|
||||
}
|
||||
67
apps/sim/tools/attio/get_note.ts
Normal file
67
apps/sim/tools/attio/get_note.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioGetNoteParams, AttioGetNoteResponse } from './types'
|
||||
import { NOTE_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioGetNote')
|
||||
|
||||
export const attioGetNoteTool: ToolConfig<AttioGetNoteParams, AttioGetNoteResponse> = {
|
||||
id: 'attio_get_note',
|
||||
name: 'Attio Get Note',
|
||||
description: 'Get a single note 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',
|
||||
},
|
||||
noteId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the note to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/notes/${params.noteId}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 note')
|
||||
}
|
||||
const note = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
noteId: note.id?.note_id ?? null,
|
||||
parentObject: note.parent_object ?? null,
|
||||
parentRecordId: note.parent_record_id ?? null,
|
||||
title: note.title ?? null,
|
||||
contentPlaintext: note.content_plaintext ?? null,
|
||||
contentMarkdown: note.content_markdown ?? null,
|
||||
meetingId: note.meeting_id ?? null,
|
||||
tags: note.tags ?? [],
|
||||
createdByActor: note.created_by_actor ?? null,
|
||||
createdAt: note.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: NOTE_OUTPUT_PROPERTIES,
|
||||
}
|
||||
62
apps/sim/tools/attio/get_object.ts
Normal file
62
apps/sim/tools/attio/get_object.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioGetObjectParams, AttioGetObjectResponse } from './types'
|
||||
import { OBJECT_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioGetObject')
|
||||
|
||||
export const attioGetObjectTool: ToolConfig<AttioGetObjectParams, AttioGetObjectResponse> = {
|
||||
id: 'attio_get_object',
|
||||
name: 'Attio Get Object',
|
||||
description: 'Get a single object by ID or slug',
|
||||
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',
|
||||
},
|
||||
object: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The object ID or slug (e.g. people, companies)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/objects/${params.object}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 object')
|
||||
}
|
||||
const obj = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
objectId: obj.id?.object_id ?? null,
|
||||
apiSlug: obj.api_slug ?? null,
|
||||
singularNoun: obj.singular_noun ?? null,
|
||||
pluralNoun: obj.plural_noun ?? null,
|
||||
createdAt: obj.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: OBJECT_OUTPUT_PROPERTIES,
|
||||
}
|
||||
71
apps/sim/tools/attio/get_record.ts
Normal file
71
apps/sim/tools/attio/get_record.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioGetRecordParams, AttioGetRecordResponse } from './types'
|
||||
import { RECORD_OBJECT_OUTPUT } from './types'
|
||||
|
||||
const logger = createLogger('AttioGetRecord')
|
||||
|
||||
export const attioGetRecordTool: ToolConfig<AttioGetRecordParams, AttioGetRecordResponse> = {
|
||||
id: 'attio_get_record',
|
||||
name: 'Attio Get Record',
|
||||
description: 'Get a single record 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',
|
||||
},
|
||||
objectType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The object type slug (e.g. people, companies)',
|
||||
},
|
||||
recordId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the record to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.attio.com/v2/objects/${params.objectType}/records/${params.recordId}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 record')
|
||||
}
|
||||
const record = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
record,
|
||||
recordId: record.id?.record_id ?? null,
|
||||
webUrl: record.web_url ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
record: RECORD_OBJECT_OUTPUT,
|
||||
recordId: { type: 'string', description: 'The record ID' },
|
||||
webUrl: { type: 'string', description: 'URL to view the record in Attio' },
|
||||
},
|
||||
}
|
||||
74
apps/sim/tools/attio/get_thread.ts
Normal file
74
apps/sim/tools/attio/get_thread.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioGetThreadParams, AttioGetThreadResponse } from './types'
|
||||
import { THREAD_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioGetThread')
|
||||
|
||||
export const attioGetThreadTool: ToolConfig<AttioGetThreadParams, AttioGetThreadResponse> = {
|
||||
id: 'attio_get_thread',
|
||||
name: 'Attio Get Thread',
|
||||
description: 'Get a single comment thread 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',
|
||||
},
|
||||
threadId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The thread ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/threads/${params.threadId}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 thread')
|
||||
}
|
||||
const t = data.data
|
||||
const comments =
|
||||
(
|
||||
t.comments as Array<{
|
||||
id?: { comment_id?: string }
|
||||
content_plaintext?: string
|
||||
author?: { type?: string; id?: string }
|
||||
created_at?: string
|
||||
}>
|
||||
)?.map((c) => ({
|
||||
commentId: c.id?.comment_id ?? null,
|
||||
contentPlaintext: c.content_plaintext ?? null,
|
||||
author: c.author ? { type: c.author.type ?? null, id: c.author.id ?? null } : null,
|
||||
createdAt: c.created_at ?? null,
|
||||
})) ?? []
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
threadId: t.id?.thread_id ?? null,
|
||||
comments,
|
||||
createdAt: t.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: THREAD_OUTPUT_PROPERTIES,
|
||||
}
|
||||
69
apps/sim/tools/attio/get_webhook.ts
Normal file
69
apps/sim/tools/attio/get_webhook.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioGetWebhookParams, AttioGetWebhookResponse } from './types'
|
||||
import { WEBHOOK_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioGetWebhook')
|
||||
|
||||
export const attioGetWebhookTool: ToolConfig<AttioGetWebhookParams, AttioGetWebhookResponse> = {
|
||||
id: 'attio_get_webhook',
|
||||
name: 'Attio Get Webhook',
|
||||
description: 'Get a single webhook 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',
|
||||
},
|
||||
webhookId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The webhook ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/webhooks/${params.webhookId}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 webhook')
|
||||
}
|
||||
const w = data.data
|
||||
const subs =
|
||||
(w.subscriptions as Array<{ event_type?: string; filter?: unknown }>)?.map(
|
||||
(s: { event_type?: string; filter?: unknown }) => ({
|
||||
eventType: s.event_type ?? null,
|
||||
filter: s.filter ?? null,
|
||||
})
|
||||
) ?? []
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
webhookId: w.id?.webhook_id ?? null,
|
||||
targetUrl: w.target_url ?? null,
|
||||
subscriptions: subs,
|
||||
status: w.status ?? null,
|
||||
createdAt: w.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: WEBHOOK_OUTPUT_PROPERTIES,
|
||||
}
|
||||
40
apps/sim/tools/attio/index.ts
Normal file
40
apps/sim/tools/attio/index.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export { attioAssertRecordTool } from './assert_record'
|
||||
export { attioCreateCommentTool } from './create_comment'
|
||||
export { attioCreateListTool } from './create_list'
|
||||
export { attioCreateListEntryTool } from './create_list_entry'
|
||||
export { attioCreateNoteTool } from './create_note'
|
||||
export { attioCreateObjectTool } from './create_object'
|
||||
export { attioCreateRecordTool } from './create_record'
|
||||
export { attioCreateTaskTool } from './create_task'
|
||||
export { attioCreateWebhookTool } from './create_webhook'
|
||||
export { attioDeleteCommentTool } from './delete_comment'
|
||||
export { attioDeleteListEntryTool } from './delete_list_entry'
|
||||
export { attioDeleteNoteTool } from './delete_note'
|
||||
export { attioDeleteRecordTool } from './delete_record'
|
||||
export { attioDeleteTaskTool } from './delete_task'
|
||||
export { attioDeleteWebhookTool } from './delete_webhook'
|
||||
export { attioGetCommentTool } from './get_comment'
|
||||
export { attioGetListTool } from './get_list'
|
||||
export { attioGetListEntryTool } from './get_list_entry'
|
||||
export { attioGetMemberTool } from './get_member'
|
||||
export { attioGetNoteTool } from './get_note'
|
||||
export { attioGetObjectTool } from './get_object'
|
||||
export { attioGetRecordTool } from './get_record'
|
||||
export { attioGetThreadTool } from './get_thread'
|
||||
export { attioGetWebhookTool } from './get_webhook'
|
||||
export { attioListListsTool } from './list_lists'
|
||||
export { attioListMembersTool } from './list_members'
|
||||
export { attioListNotesTool } from './list_notes'
|
||||
export { attioListObjectsTool } from './list_objects'
|
||||
export { attioListRecordsTool } from './list_records'
|
||||
export { attioListTasksTool } from './list_tasks'
|
||||
export { attioListThreadsTool } from './list_threads'
|
||||
export { attioListWebhooksTool } from './list_webhooks'
|
||||
export { attioQueryListEntriesTool } from './query_list_entries'
|
||||
export { attioSearchRecordsTool } from './search_records'
|
||||
export { attioUpdateListTool } from './update_list'
|
||||
export { attioUpdateListEntryTool } from './update_list_entry'
|
||||
export { attioUpdateObjectTool } from './update_object'
|
||||
export { attioUpdateRecordTool } from './update_record'
|
||||
export { attioUpdateTaskTool } from './update_task'
|
||||
export { attioUpdateWebhookTool } from './update_webhook'
|
||||
78
apps/sim/tools/attio/list_lists.ts
Normal file
78
apps/sim/tools/attio/list_lists.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioListListsParams, AttioListListsResponse } from './types'
|
||||
import { LIST_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioListLists')
|
||||
|
||||
export const attioListListsTool: ToolConfig<AttioListListsParams, AttioListListsResponse> = {
|
||||
id: 'attio_list_lists',
|
||||
name: 'Attio List Lists',
|
||||
description: 'List all lists in the Attio workspace',
|
||||
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',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.attio.com/v2/lists',
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 list lists')
|
||||
}
|
||||
const lists = (data.data ?? []).map((list: Record<string, unknown>) => {
|
||||
const id = list.id as { list_id?: string } | undefined
|
||||
const actor = list.created_by_actor as { type?: string; id?: string } | undefined
|
||||
return {
|
||||
listId: id?.list_id ?? null,
|
||||
apiSlug: (list.api_slug as string) ?? null,
|
||||
name: (list.name as string) ?? null,
|
||||
parentObject: Array.isArray(list.parent_object)
|
||||
? (list.parent_object[0] ?? null)
|
||||
: ((list.parent_object as string) ?? null),
|
||||
workspaceAccess: (list.workspace_access as string) ?? null,
|
||||
workspaceMemberAccess: list.workspace_member_access ?? null,
|
||||
createdByActor: actor ? { type: actor.type ?? null, id: actor.id ?? null } : null,
|
||||
createdAt: (list.created_at as string) ?? null,
|
||||
}
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
lists,
|
||||
count: lists.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
lists: {
|
||||
type: 'array',
|
||||
description: 'Array of lists',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: LIST_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of lists returned' },
|
||||
},
|
||||
}
|
||||
74
apps/sim/tools/attio/list_members.ts
Normal file
74
apps/sim/tools/attio/list_members.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioListMembersParams, AttioListMembersResponse } from './types'
|
||||
import { MEMBER_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioListMembers')
|
||||
|
||||
export const attioListMembersTool: ToolConfig<AttioListMembersParams, AttioListMembersResponse> = {
|
||||
id: 'attio_list_members',
|
||||
name: 'Attio List Members',
|
||||
description: 'List all workspace members in 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',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.attio.com/v2/workspace_members',
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 list workspace members')
|
||||
}
|
||||
const members = (data.data ?? []).map((m: Record<string, unknown>) => {
|
||||
const id = m.id as { workspace_member_id?: string } | undefined
|
||||
return {
|
||||
memberId: id?.workspace_member_id ?? null,
|
||||
firstName: (m.first_name as string) ?? null,
|
||||
lastName: (m.last_name as string) ?? null,
|
||||
avatarUrl: (m.avatar_url as string) ?? null,
|
||||
emailAddress: (m.email_address as string) ?? null,
|
||||
accessLevel: (m.access_level as string) ?? null,
|
||||
createdAt: (m.created_at as string) ?? null,
|
||||
}
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
members,
|
||||
count: members.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
members: {
|
||||
type: 'array',
|
||||
description: 'Array of workspace members',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: MEMBER_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of members returned' },
|
||||
},
|
||||
}
|
||||
109
apps/sim/tools/attio/list_notes.ts
Normal file
109
apps/sim/tools/attio/list_notes.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioListNotesParams, AttioListNotesResponse } from './types'
|
||||
import { NOTE_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioListNotes')
|
||||
|
||||
export const attioListNotesTool: ToolConfig<AttioListNotesParams, AttioListNotesResponse> = {
|
||||
id: 'attio_list_notes',
|
||||
name: 'Attio List Notes',
|
||||
description: 'List notes in Attio, optionally filtered by parent record',
|
||||
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',
|
||||
},
|
||||
parentObject: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Object type slug to filter notes by (e.g. people, companies)',
|
||||
},
|
||||
parentRecordId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Record ID to filter notes by',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of notes to return (default 10, max 50)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of notes to skip for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const searchParams = new URLSearchParams()
|
||||
if (params.parentObject) searchParams.set('parent_object', params.parentObject)
|
||||
if (params.parentRecordId) searchParams.set('parent_record_id', params.parentRecordId)
|
||||
if (params.limit !== undefined) searchParams.set('limit', String(params.limit))
|
||||
if (params.offset !== undefined) searchParams.set('offset', String(params.offset))
|
||||
const qs = searchParams.toString()
|
||||
return `https://api.attio.com/v2/notes${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 list notes')
|
||||
}
|
||||
const notes = (data.data ?? []).map((note: Record<string, unknown>) => {
|
||||
const noteId = note.id as { note_id?: string } | undefined
|
||||
return {
|
||||
noteId: noteId?.note_id ?? null,
|
||||
parentObject: (note.parent_object as string) ?? null,
|
||||
parentRecordId: (note.parent_record_id as string) ?? null,
|
||||
title: (note.title as string) ?? null,
|
||||
contentPlaintext: (note.content_plaintext as string) ?? null,
|
||||
contentMarkdown: (note.content_markdown as string) ?? null,
|
||||
meetingId: (note.meeting_id as string) ?? null,
|
||||
tags: (note.tags as unknown[]) ?? [],
|
||||
createdByActor: note.created_by_actor ?? null,
|
||||
createdAt: (note.created_at as string) ?? null,
|
||||
}
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
notes,
|
||||
count: notes.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
notes: {
|
||||
type: 'array',
|
||||
description: 'Array of notes',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: NOTE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of notes returned' },
|
||||
},
|
||||
}
|
||||
77
apps/sim/tools/attio/list_objects.ts
Normal file
77
apps/sim/tools/attio/list_objects.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioListObjectsParams, AttioListObjectsResponse } from './types'
|
||||
import { OBJECT_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioListObjects')
|
||||
|
||||
export const attioListObjectsTool: ToolConfig<AttioListObjectsParams, AttioListObjectsResponse> = {
|
||||
id: 'attio_list_objects',
|
||||
name: 'Attio List Objects',
|
||||
description: 'List all objects (system and custom) in the Attio workspace',
|
||||
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',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.attio.com/v2/objects',
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 list objects')
|
||||
}
|
||||
const objects = (data.data ?? []).map(
|
||||
(obj: {
|
||||
id?: { object_id?: string }
|
||||
api_slug?: string
|
||||
singular_noun?: string
|
||||
plural_noun?: string
|
||||
created_at?: string
|
||||
}) => ({
|
||||
objectId: obj.id?.object_id ?? null,
|
||||
apiSlug: obj.api_slug ?? null,
|
||||
singularNoun: obj.singular_noun ?? null,
|
||||
pluralNoun: obj.plural_noun ?? null,
|
||||
createdAt: obj.created_at ?? null,
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
objects,
|
||||
count: objects.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
objects: {
|
||||
type: 'array',
|
||||
description: 'Array of objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: OBJECT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of objects returned' },
|
||||
},
|
||||
}
|
||||
107
apps/sim/tools/attio/list_records.ts
Normal file
107
apps/sim/tools/attio/list_records.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioListRecordsParams, AttioListRecordsResponse } from './types'
|
||||
import { RECORDS_ARRAY_OUTPUT } from './types'
|
||||
|
||||
const logger = createLogger('AttioListRecords')
|
||||
|
||||
export const attioListRecordsTool: ToolConfig<AttioListRecordsParams, AttioListRecordsResponse> = {
|
||||
id: 'attio_list_records',
|
||||
name: 'Attio List Records',
|
||||
description: 'Query and list records for a given object type (e.g. people, companies)',
|
||||
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',
|
||||
},
|
||||
objectType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The object type slug (e.g. people, companies)',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON filter object for querying records',
|
||||
},
|
||||
sorts: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON array of sort objects, e.g. [{"direction":"asc","attribute":"name"}]',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of records to return (default 500)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of records to skip for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/objects/${params.objectType}/records/query`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
if (params.filter) {
|
||||
try {
|
||||
body.filter = JSON.parse(params.filter)
|
||||
} catch {
|
||||
body.filter = params.filter
|
||||
}
|
||||
}
|
||||
if (params.sorts) {
|
||||
try {
|
||||
body.sorts = JSON.parse(params.sorts)
|
||||
} catch {
|
||||
body.sorts = params.sorts
|
||||
}
|
||||
}
|
||||
if (params.limit !== undefined) body.limit = params.limit
|
||||
if (params.offset !== undefined) body.offset = params.offset
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
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 list records')
|
||||
}
|
||||
const records = data.data ?? []
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
records,
|
||||
count: records.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
records: RECORDS_ARRAY_OUTPUT,
|
||||
count: { type: 'number', description: 'Number of records returned' },
|
||||
},
|
||||
}
|
||||
147
apps/sim/tools/attio/list_tasks.ts
Normal file
147
apps/sim/tools/attio/list_tasks.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioListTasksParams, AttioListTasksResponse } from './types'
|
||||
import { TASK_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioListTasks')
|
||||
|
||||
export const attioListTasksTool: ToolConfig<AttioListTasksParams, AttioListTasksResponse> = {
|
||||
id: 'attio_list_tasks',
|
||||
name: 'Attio List Tasks',
|
||||
description: 'List tasks in Attio, optionally filtered by record, assignee, or completion status',
|
||||
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',
|
||||
},
|
||||
linkedObject: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Object type slug to filter tasks by (requires linkedRecordId)',
|
||||
},
|
||||
linkedRecordId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Record ID to filter tasks by (requires linkedObject)',
|
||||
},
|
||||
assignee: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Assignee email or member ID to filter by',
|
||||
},
|
||||
isCompleted: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by completion status',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: created_at:asc or created_at:desc',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of tasks to return (default 500)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of tasks to skip for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const searchParams = new URLSearchParams()
|
||||
if (params.linkedObject) searchParams.set('linked_object', params.linkedObject)
|
||||
if (params.linkedRecordId) searchParams.set('linked_record_id', params.linkedRecordId)
|
||||
if (params.assignee) searchParams.set('assignee', params.assignee)
|
||||
if (params.isCompleted !== undefined) {
|
||||
searchParams.set('is_completed', String(params.isCompleted))
|
||||
}
|
||||
if (params.sort) searchParams.set('sort', params.sort)
|
||||
if (params.limit !== undefined) searchParams.set('limit', String(params.limit))
|
||||
if (params.offset !== undefined) searchParams.set('offset', String(params.offset))
|
||||
const qs = searchParams.toString()
|
||||
return `https://api.attio.com/v2/tasks${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 list tasks')
|
||||
}
|
||||
const tasks = (data.data ?? []).map((task: Record<string, unknown>) => {
|
||||
const taskId = task.id as { task_id?: string } | undefined
|
||||
const linkedRecords =
|
||||
(
|
||||
task.linked_records as Array<{ target_object_id?: string; target_record_id?: string }>
|
||||
)?.map((r) => ({
|
||||
targetObjectId: r.target_object_id ?? null,
|
||||
targetRecordId: r.target_record_id ?? null,
|
||||
})) ?? []
|
||||
const assignees =
|
||||
(
|
||||
task.assignees as Array<{
|
||||
referenced_actor_type?: string
|
||||
referenced_actor_id?: string
|
||||
}>
|
||||
)?.map((a) => ({
|
||||
type: a.referenced_actor_type ?? null,
|
||||
id: a.referenced_actor_id ?? null,
|
||||
})) ?? []
|
||||
return {
|
||||
taskId: taskId?.task_id ?? null,
|
||||
content: (task.content_plaintext as string) ?? null,
|
||||
deadlineAt: (task.deadline_at as string) ?? null,
|
||||
isCompleted: (task.is_completed as boolean) ?? false,
|
||||
linkedRecords,
|
||||
assignees,
|
||||
createdByActor: task.created_by_actor ?? null,
|
||||
createdAt: (task.created_at as string) ?? null,
|
||||
}
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tasks,
|
||||
count: tasks.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tasks: {
|
||||
type: 'array',
|
||||
description: 'Array of tasks',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: TASK_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of tasks returned' },
|
||||
},
|
||||
}
|
||||
152
apps/sim/tools/attio/list_threads.ts
Normal file
152
apps/sim/tools/attio/list_threads.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioListThreadsParams, AttioListThreadsResponse } from './types'
|
||||
|
||||
const logger = createLogger('AttioListThreads')
|
||||
|
||||
export const attioListThreadsTool: ToolConfig<AttioListThreadsParams, AttioListThreadsResponse> = {
|
||||
id: 'attio_list_threads',
|
||||
name: 'Attio List Threads',
|
||||
description: 'List comment threads in Attio, optionally filtered by record or list entry',
|
||||
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',
|
||||
},
|
||||
recordId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by record ID (requires object)',
|
||||
},
|
||||
object: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Object slug to filter by (requires recordId)',
|
||||
},
|
||||
entryId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by list entry ID (requires list)',
|
||||
},
|
||||
list: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'List ID or slug to filter by (requires entryId)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of threads to return (max 50)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of threads to skip for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const searchParams = new URLSearchParams()
|
||||
if (params.recordId) searchParams.set('record_id', params.recordId)
|
||||
if (params.object) searchParams.set('object', params.object)
|
||||
if (params.entryId) searchParams.set('entry_id', params.entryId)
|
||||
if (params.list) searchParams.set('list', params.list)
|
||||
if (params.limit !== undefined) searchParams.set('limit', String(params.limit))
|
||||
if (params.offset !== undefined) searchParams.set('offset', String(params.offset))
|
||||
const qs = searchParams.toString()
|
||||
return `https://api.attio.com/v2/threads${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 list threads')
|
||||
}
|
||||
const threads = (data.data ?? []).map((t: Record<string, unknown>) => {
|
||||
const id = t.id as { thread_id?: string } | undefined
|
||||
const comments =
|
||||
(
|
||||
t.comments as Array<{
|
||||
id?: { comment_id?: string }
|
||||
content_plaintext?: string
|
||||
author?: { type?: string; id?: string }
|
||||
created_at?: string
|
||||
}>
|
||||
)?.map((c) => ({
|
||||
commentId: c.id?.comment_id ?? null,
|
||||
contentPlaintext: c.content_plaintext ?? null,
|
||||
author: c.author ? { type: c.author.type ?? null, id: c.author.id ?? null } : null,
|
||||
createdAt: c.created_at ?? null,
|
||||
})) ?? []
|
||||
return {
|
||||
threadId: id?.thread_id ?? null,
|
||||
comments,
|
||||
createdAt: (t.created_at as string) ?? null,
|
||||
}
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
threads,
|
||||
count: threads.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
threads: {
|
||||
type: 'array',
|
||||
description: 'Array of threads',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
threadId: { type: 'string', description: 'The thread ID' },
|
||||
comments: {
|
||||
type: 'array',
|
||||
description: 'Comments in the thread',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
commentId: { type: 'string', description: 'The comment ID' },
|
||||
contentPlaintext: { type: 'string', description: 'Comment content' },
|
||||
author: {
|
||||
type: 'object',
|
||||
description: 'Comment author',
|
||||
properties: {
|
||||
type: { type: 'string', description: 'Actor type' },
|
||||
id: { type: 'string', description: 'Actor ID' },
|
||||
},
|
||||
},
|
||||
createdAt: { type: 'string', description: 'When the comment was created' },
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: { type: 'string', description: 'When the thread was created' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of threads returned' },
|
||||
},
|
||||
}
|
||||
96
apps/sim/tools/attio/list_webhooks.ts
Normal file
96
apps/sim/tools/attio/list_webhooks.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioListWebhooksParams, AttioListWebhooksResponse } from './types'
|
||||
import { WEBHOOK_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioListWebhooks')
|
||||
|
||||
export const attioListWebhooksTool: ToolConfig<AttioListWebhooksParams, AttioListWebhooksResponse> =
|
||||
{
|
||||
id: 'attio_list_webhooks',
|
||||
name: 'Attio List Webhooks',
|
||||
description: 'List all webhooks in the Attio workspace',
|
||||
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',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of webhooks to return',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of webhooks to skip for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const searchParams = new URLSearchParams()
|
||||
if (params.limit !== undefined) searchParams.set('limit', String(params.limit))
|
||||
if (params.offset !== undefined) searchParams.set('offset', String(params.offset))
|
||||
const qs = searchParams.toString()
|
||||
return `https://api.attio.com/v2/webhooks${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
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 list webhooks')
|
||||
}
|
||||
const webhooks = (data.data ?? []).map((w: Record<string, unknown>) => {
|
||||
const id = w.id as { webhook_id?: string } | undefined
|
||||
const subs =
|
||||
(w.subscriptions as Array<{ event_type?: string; filter?: unknown }>)?.map((s) => ({
|
||||
eventType: s.event_type ?? null,
|
||||
filter: s.filter ?? null,
|
||||
})) ?? []
|
||||
return {
|
||||
webhookId: id?.webhook_id ?? null,
|
||||
targetUrl: (w.target_url as string) ?? null,
|
||||
subscriptions: subs,
|
||||
status: (w.status as string) ?? null,
|
||||
createdAt: (w.created_at as string) ?? null,
|
||||
}
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
webhooks,
|
||||
count: webhooks.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
webhooks: {
|
||||
type: 'array',
|
||||
description: 'Array of webhooks',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: WEBHOOK_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of webhooks returned' },
|
||||
},
|
||||
}
|
||||
129
apps/sim/tools/attio/query_list_entries.ts
Normal file
129
apps/sim/tools/attio/query_list_entries.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioQueryListEntriesParams, AttioQueryListEntriesResponse } from './types'
|
||||
import { LIST_ENTRY_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioQueryListEntries')
|
||||
|
||||
export const attioQueryListEntriesTool: ToolConfig<
|
||||
AttioQueryListEntriesParams,
|
||||
AttioQueryListEntriesResponse
|
||||
> = {
|
||||
id: 'attio_query_list_entries',
|
||||
name: 'Attio Query List Entries',
|
||||
description: 'Query entries in an Attio list with optional filter, sort, and pagination',
|
||||
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',
|
||||
},
|
||||
list: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The list ID or slug',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON filter object for querying entries',
|
||||
},
|
||||
sorts: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of sort objects (e.g. [{"attribute":"created_at","direction":"desc"}])',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of entries to return (default 500)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of entries to skip for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/lists/${params.list}/entries/query`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
if (params.filter) {
|
||||
try {
|
||||
body.filter =
|
||||
typeof params.filter === 'string' ? JSON.parse(params.filter) : params.filter
|
||||
} catch {
|
||||
body.filter = {}
|
||||
}
|
||||
}
|
||||
if (params.sorts) {
|
||||
try {
|
||||
body.sorts = typeof params.sorts === 'string' ? JSON.parse(params.sorts) : params.sorts
|
||||
} catch {
|
||||
body.sorts = []
|
||||
}
|
||||
}
|
||||
if (params.limit !== undefined) body.limit = params.limit
|
||||
if (params.offset !== undefined) body.offset = params.offset
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
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 query list entries')
|
||||
}
|
||||
const entries = (data.data ?? []).map((entry: Record<string, unknown>) => {
|
||||
const id = entry.id as { list_id?: string; entry_id?: string } | undefined
|
||||
return {
|
||||
entryId: id?.entry_id ?? null,
|
||||
listId: id?.list_id ?? null,
|
||||
parentRecordId: (entry.parent_record_id as string) ?? null,
|
||||
parentObject: (entry.parent_object as string) ?? null,
|
||||
createdAt: (entry.created_at as string) ?? null,
|
||||
entryValues: (entry.entry_values as Record<string, unknown>) ?? {},
|
||||
}
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
entries,
|
||||
count: entries.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
entries: {
|
||||
type: 'array',
|
||||
description: 'Array of list entries',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: LIST_ENTRY_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of entries returned' },
|
||||
},
|
||||
}
|
||||
115
apps/sim/tools/attio/search_records.ts
Normal file
115
apps/sim/tools/attio/search_records.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioSearchRecordsParams, AttioSearchRecordsResponse } from './types'
|
||||
|
||||
const logger = createLogger('AttioSearchRecords')
|
||||
|
||||
export const attioSearchRecordsTool: ToolConfig<
|
||||
AttioSearchRecordsParams,
|
||||
AttioSearchRecordsResponse
|
||||
> = {
|
||||
id: 'attio_search_records',
|
||||
name: 'Attio Search Records',
|
||||
description: 'Fuzzy search for records across object types in 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',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The search query (max 256 characters)',
|
||||
},
|
||||
objects: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated object slugs to search (e.g. people,companies)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (1-25, default 25)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.attio.com/v2/objects/records/search',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const objects = params.objects
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
return {
|
||||
query: params.query,
|
||||
objects,
|
||||
limit: params.limit ?? 25,
|
||||
request_as: { type: 'workspace' },
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
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 search records')
|
||||
}
|
||||
const results = (data.data ?? []).map(
|
||||
(item: {
|
||||
id?: { workspace_id?: string; object_id?: string; record_id?: string }
|
||||
object_slug?: string
|
||||
record_text?: string
|
||||
record_image?: string
|
||||
}) => ({
|
||||
recordId: item.id?.record_id ?? null,
|
||||
objectId: item.id?.object_id ?? null,
|
||||
objectSlug: item.object_slug ?? null,
|
||||
recordText: item.record_text ?? null,
|
||||
recordImage: item.record_image ?? null,
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
results,
|
||||
count: results.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
results: {
|
||||
type: 'array',
|
||||
description: 'Search results',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
recordId: { type: 'string', description: 'The record ID' },
|
||||
objectId: { type: 'string', description: 'The object type ID' },
|
||||
objectSlug: { type: 'string', description: 'The object type slug' },
|
||||
recordText: { type: 'string', description: 'Display text for the record' },
|
||||
recordImage: { type: 'string', description: 'Image URL for the record', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of results returned' },
|
||||
},
|
||||
}
|
||||
1121
apps/sim/tools/attio/types.ts
Normal file
1121
apps/sim/tools/attio/types.ts
Normal file
File diff suppressed because it is too large
Load Diff
112
apps/sim/tools/attio/update_list.ts
Normal file
112
apps/sim/tools/attio/update_list.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioUpdateListParams, AttioUpdateListResponse } from './types'
|
||||
import { LIST_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioUpdateList')
|
||||
|
||||
export const attioUpdateListTool: ToolConfig<AttioUpdateListParams, AttioUpdateListResponse> = {
|
||||
id: 'attio_update_list',
|
||||
name: 'Attio Update List',
|
||||
description: 'Update a list in 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',
|
||||
},
|
||||
list: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The list ID or slug to update',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New name for the list',
|
||||
},
|
||||
apiSlug: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New API slug for the list',
|
||||
},
|
||||
workspaceAccess: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'New workspace-level access: full-access, read-and-write, or read-only (omit for private)',
|
||||
},
|
||||
workspaceMemberAccess: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of member access entries, e.g. [{"workspace_member_id":"...","level":"read-and-write"}]',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/lists/${params.list}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const data: Record<string, unknown> = {}
|
||||
if (params.name !== undefined) data.name = params.name
|
||||
if (params.apiSlug !== undefined) data.api_slug = params.apiSlug
|
||||
if (params.workspaceAccess !== undefined) data.workspace_access = params.workspaceAccess
|
||||
if (params.workspaceMemberAccess !== undefined) {
|
||||
try {
|
||||
data.workspace_member_access =
|
||||
typeof params.workspaceMemberAccess === 'string'
|
||||
? JSON.parse(params.workspaceMemberAccess)
|
||||
: params.workspaceMemberAccess
|
||||
} catch {
|
||||
data.workspace_member_access = params.workspaceMemberAccess
|
||||
}
|
||||
}
|
||||
return { data }
|
||||
},
|
||||
},
|
||||
|
||||
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 update list')
|
||||
}
|
||||
const list = data.data
|
||||
const actor = list.created_by_actor as { type?: string; id?: string } | undefined
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
listId: list.id?.list_id ?? null,
|
||||
apiSlug: list.api_slug ?? null,
|
||||
name: list.name ?? null,
|
||||
parentObject: Array.isArray(list.parent_object)
|
||||
? (list.parent_object[0] ?? null)
|
||||
: (list.parent_object ?? null),
|
||||
workspaceAccess: list.workspace_access ?? null,
|
||||
workspaceMemberAccess: list.workspace_member_access ?? null,
|
||||
createdByActor: actor ? { type: actor.type ?? null, id: actor.id ?? null } : null,
|
||||
createdAt: list.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: LIST_OUTPUT_PROPERTIES,
|
||||
}
|
||||
91
apps/sim/tools/attio/update_list_entry.ts
Normal file
91
apps/sim/tools/attio/update_list_entry.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioUpdateListEntryParams, AttioUpdateListEntryResponse } from './types'
|
||||
import { LIST_ENTRY_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioUpdateListEntry')
|
||||
|
||||
export const attioUpdateListEntryTool: ToolConfig<
|
||||
AttioUpdateListEntryParams,
|
||||
AttioUpdateListEntryResponse
|
||||
> = {
|
||||
id: 'attio_update_list_entry',
|
||||
name: 'Attio Update List Entry',
|
||||
description: 'Update entry attribute values on an Attio list entry (appends multiselect values)',
|
||||
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',
|
||||
},
|
||||
list: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The list ID or slug',
|
||||
},
|
||||
entryId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The entry ID to update',
|
||||
},
|
||||
entryValues: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object of entry attribute values to update',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/lists/${params.list}/entries/${params.entryId}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let entryValues: Record<string, unknown> = {}
|
||||
try {
|
||||
entryValues =
|
||||
typeof params.entryValues === 'string'
|
||||
? JSON.parse(params.entryValues)
|
||||
: params.entryValues
|
||||
} catch {
|
||||
entryValues = {}
|
||||
}
|
||||
return { data: { entry_values: entryValues } }
|
||||
},
|
||||
},
|
||||
|
||||
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 update list entry')
|
||||
}
|
||||
const entry = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
entryId: entry.id?.entry_id ?? null,
|
||||
listId: entry.id?.list_id ?? null,
|
||||
parentRecordId: entry.parent_record_id ?? null,
|
||||
parentObject: entry.parent_object ?? null,
|
||||
createdAt: entry.created_at ?? null,
|
||||
entryValues: entry.entry_values ?? {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: LIST_ENTRY_OUTPUT_PROPERTIES,
|
||||
}
|
||||
89
apps/sim/tools/attio/update_object.ts
Normal file
89
apps/sim/tools/attio/update_object.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioUpdateObjectParams, AttioUpdateObjectResponse } from './types'
|
||||
import { OBJECT_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioUpdateObject')
|
||||
|
||||
export const attioUpdateObjectTool: ToolConfig<AttioUpdateObjectParams, AttioUpdateObjectResponse> =
|
||||
{
|
||||
id: 'attio_update_object',
|
||||
name: 'Attio Update Object',
|
||||
description: 'Update a custom object in 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',
|
||||
},
|
||||
object: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The object ID or slug to update',
|
||||
},
|
||||
apiSlug: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New API slug',
|
||||
},
|
||||
singularNoun: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New singular display name',
|
||||
},
|
||||
pluralNoun: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New plural display name',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/objects/${params.object}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const data: Record<string, unknown> = {}
|
||||
if (params.apiSlug !== undefined) data.api_slug = params.apiSlug
|
||||
if (params.singularNoun !== undefined) data.singular_noun = params.singularNoun
|
||||
if (params.pluralNoun !== undefined) data.plural_noun = params.pluralNoun
|
||||
return { data }
|
||||
},
|
||||
},
|
||||
|
||||
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 update object')
|
||||
}
|
||||
const obj = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
objectId: obj.id?.object_id ?? null,
|
||||
apiSlug: obj.api_slug ?? null,
|
||||
singularNoun: obj.singular_noun ?? null,
|
||||
pluralNoun: obj.plural_noun ?? null,
|
||||
createdAt: obj.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: OBJECT_OUTPUT_PROPERTIES,
|
||||
}
|
||||
88
apps/sim/tools/attio/update_record.ts
Normal file
88
apps/sim/tools/attio/update_record.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioUpdateRecordParams, AttioUpdateRecordResponse } from './types'
|
||||
import { RECORD_OBJECT_OUTPUT } from './types'
|
||||
|
||||
const logger = createLogger('AttioUpdateRecord')
|
||||
|
||||
export const attioUpdateRecordTool: ToolConfig<AttioUpdateRecordParams, AttioUpdateRecordResponse> =
|
||||
{
|
||||
id: 'attio_update_record',
|
||||
name: 'Attio Update Record',
|
||||
description: 'Update an existing record in Attio (appends multiselect values)',
|
||||
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',
|
||||
},
|
||||
objectType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The object type slug (e.g. people, companies)',
|
||||
},
|
||||
recordId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the record to update',
|
||||
},
|
||||
values: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object of attribute values to update',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.attio.com/v2/objects/${params.objectType}/records/${params.recordId}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let values: Record<string, unknown>
|
||||
try {
|
||||
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
|
||||
} catch {
|
||||
values = {}
|
||||
}
|
||||
return { data: { values } }
|
||||
},
|
||||
},
|
||||
|
||||
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 update record')
|
||||
}
|
||||
const record = data.data
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
record,
|
||||
recordId: record.id?.record_id ?? null,
|
||||
webUrl: record.web_url ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
record: RECORD_OBJECT_OUTPUT,
|
||||
recordId: { type: 'string', description: 'The ID of the updated record' },
|
||||
webUrl: { type: 'string', description: 'URL to view the record in Attio' },
|
||||
},
|
||||
}
|
||||
126
apps/sim/tools/attio/update_task.ts
Normal file
126
apps/sim/tools/attio/update_task.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioUpdateTaskParams, AttioUpdateTaskResponse } from './types'
|
||||
import { TASK_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioUpdateTask')
|
||||
|
||||
export const attioUpdateTaskTool: ToolConfig<AttioUpdateTaskParams, AttioUpdateTaskResponse> = {
|
||||
id: 'attio_update_task',
|
||||
name: 'Attio Update Task',
|
||||
description: 'Update a task in Attio (deadline, completion status, linked records, assignees)',
|
||||
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 update',
|
||||
},
|
||||
deadlineAt: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New deadline in ISO 8601 format',
|
||||
},
|
||||
isCompleted: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether the task is completed',
|
||||
},
|
||||
linkedRecords: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON array of linked records',
|
||||
},
|
||||
assignees: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON array of assignees',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/tasks/${params.taskId}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const data: Record<string, unknown> = {}
|
||||
if (params.deadlineAt !== undefined) data.deadline_at = params.deadlineAt || null
|
||||
if (params.isCompleted !== undefined) data.is_completed = params.isCompleted
|
||||
if (params.linkedRecords) {
|
||||
try {
|
||||
data.linked_records =
|
||||
typeof params.linkedRecords === 'string'
|
||||
? JSON.parse(params.linkedRecords)
|
||||
: params.linkedRecords
|
||||
} catch {
|
||||
data.linked_records = []
|
||||
}
|
||||
}
|
||||
if (params.assignees) {
|
||||
try {
|
||||
data.assignees =
|
||||
typeof params.assignees === 'string' ? JSON.parse(params.assignees) : params.assignees
|
||||
} catch {
|
||||
data.assignees = []
|
||||
}
|
||||
}
|
||||
return { data }
|
||||
},
|
||||
},
|
||||
|
||||
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 update 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,
|
||||
}
|
||||
100
apps/sim/tools/attio/update_webhook.ts
Normal file
100
apps/sim/tools/attio/update_webhook.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioUpdateWebhookParams, AttioUpdateWebhookResponse } from './types'
|
||||
import { WEBHOOK_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioUpdateWebhook')
|
||||
|
||||
export const attioUpdateWebhookTool: ToolConfig<
|
||||
AttioUpdateWebhookParams,
|
||||
AttioUpdateWebhookResponse
|
||||
> = {
|
||||
id: 'attio_update_webhook',
|
||||
name: 'Attio Update Webhook',
|
||||
description: 'Update a webhook in Attio (target URL and/or subscriptions)',
|
||||
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',
|
||||
},
|
||||
webhookId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The webhook ID to update',
|
||||
},
|
||||
targetUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New HTTPS target URL',
|
||||
},
|
||||
subscriptions: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New JSON array of subscriptions',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.attio.com/v2/webhooks/${params.webhookId}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const data: Record<string, unknown> = {}
|
||||
if (params.targetUrl !== undefined) data.target_url = params.targetUrl
|
||||
if (params.subscriptions) {
|
||||
try {
|
||||
data.subscriptions =
|
||||
typeof params.subscriptions === 'string'
|
||||
? JSON.parse(params.subscriptions)
|
||||
: params.subscriptions
|
||||
} catch {
|
||||
data.subscriptions = []
|
||||
}
|
||||
}
|
||||
return { data }
|
||||
},
|
||||
},
|
||||
|
||||
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 update webhook')
|
||||
}
|
||||
const w = data.data
|
||||
const subs =
|
||||
(w.subscriptions as Array<{ event_type?: string; filter?: unknown }>)?.map(
|
||||
(s: { event_type?: string; filter?: unknown }) => ({
|
||||
eventType: s.event_type ?? null,
|
||||
filter: s.filter ?? null,
|
||||
})
|
||||
) ?? []
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
webhookId: w.id?.webhook_id ?? null,
|
||||
targetUrl: w.target_url ?? null,
|
||||
subscriptions: subs,
|
||||
status: w.status ?? null,
|
||||
createdAt: w.created_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: WEBHOOK_OUTPUT_PROPERTIES,
|
||||
}
|
||||
@@ -79,6 +79,48 @@ import {
|
||||
asanaSearchTasksTool,
|
||||
asanaUpdateTaskTool,
|
||||
} from '@/tools/asana'
|
||||
import {
|
||||
attioAssertRecordTool,
|
||||
attioCreateCommentTool,
|
||||
attioCreateListEntryTool,
|
||||
attioCreateListTool,
|
||||
attioCreateNoteTool,
|
||||
attioCreateObjectTool,
|
||||
attioCreateRecordTool,
|
||||
attioCreateTaskTool,
|
||||
attioCreateWebhookTool,
|
||||
attioDeleteCommentTool,
|
||||
attioDeleteListEntryTool,
|
||||
attioDeleteNoteTool,
|
||||
attioDeleteRecordTool,
|
||||
attioDeleteTaskTool,
|
||||
attioDeleteWebhookTool,
|
||||
attioGetCommentTool,
|
||||
attioGetListEntryTool,
|
||||
attioGetListTool,
|
||||
attioGetMemberTool,
|
||||
attioGetNoteTool,
|
||||
attioGetObjectTool,
|
||||
attioGetRecordTool,
|
||||
attioGetThreadTool,
|
||||
attioGetWebhookTool,
|
||||
attioListListsTool,
|
||||
attioListMembersTool,
|
||||
attioListNotesTool,
|
||||
attioListObjectsTool,
|
||||
attioListRecordsTool,
|
||||
attioListTasksTool,
|
||||
attioListThreadsTool,
|
||||
attioListWebhooksTool,
|
||||
attioQueryListEntriesTool,
|
||||
attioSearchRecordsTool,
|
||||
attioUpdateListEntryTool,
|
||||
attioUpdateListTool,
|
||||
attioUpdateObjectTool,
|
||||
attioUpdateRecordTool,
|
||||
attioUpdateTaskTool,
|
||||
attioUpdateWebhookTool,
|
||||
} from '@/tools/attio'
|
||||
import { browserUseRunTaskTool } from '@/tools/browser_use'
|
||||
import {
|
||||
calcomCancelBookingTool,
|
||||
@@ -3060,6 +3102,46 @@ export const tools: Record<string, ToolConfig> = {
|
||||
airtable_get_record: airtableGetRecordTool,
|
||||
airtable_list_records: airtableListRecordsTool,
|
||||
airtable_update_record: airtableUpdateRecordTool,
|
||||
attio_assert_record: attioAssertRecordTool,
|
||||
attio_create_comment: attioCreateCommentTool,
|
||||
attio_create_list: attioCreateListTool,
|
||||
attio_create_list_entry: attioCreateListEntryTool,
|
||||
attio_create_note: attioCreateNoteTool,
|
||||
attio_create_object: attioCreateObjectTool,
|
||||
attio_create_record: attioCreateRecordTool,
|
||||
attio_create_task: attioCreateTaskTool,
|
||||
attio_create_webhook: attioCreateWebhookTool,
|
||||
attio_delete_comment: attioDeleteCommentTool,
|
||||
attio_delete_list_entry: attioDeleteListEntryTool,
|
||||
attio_delete_note: attioDeleteNoteTool,
|
||||
attio_delete_record: attioDeleteRecordTool,
|
||||
attio_delete_task: attioDeleteTaskTool,
|
||||
attio_delete_webhook: attioDeleteWebhookTool,
|
||||
attio_get_comment: attioGetCommentTool,
|
||||
attio_get_list: attioGetListTool,
|
||||
attio_get_list_entry: attioGetListEntryTool,
|
||||
attio_get_member: attioGetMemberTool,
|
||||
attio_get_note: attioGetNoteTool,
|
||||
attio_get_object: attioGetObjectTool,
|
||||
attio_get_record: attioGetRecordTool,
|
||||
attio_get_thread: attioGetThreadTool,
|
||||
attio_get_webhook: attioGetWebhookTool,
|
||||
attio_list_lists: attioListListsTool,
|
||||
attio_list_members: attioListMembersTool,
|
||||
attio_list_notes: attioListNotesTool,
|
||||
attio_list_objects: attioListObjectsTool,
|
||||
attio_list_records: attioListRecordsTool,
|
||||
attio_list_tasks: attioListTasksTool,
|
||||
attio_list_threads: attioListThreadsTool,
|
||||
attio_list_webhooks: attioListWebhooksTool,
|
||||
attio_query_list_entries: attioQueryListEntriesTool,
|
||||
attio_search_records: attioSearchRecordsTool,
|
||||
attio_update_list: attioUpdateListTool,
|
||||
attio_update_list_entry: attioUpdateListEntryTool,
|
||||
attio_update_object: attioUpdateObjectTool,
|
||||
attio_update_record: attioUpdateRecordTool,
|
||||
attio_update_task: attioUpdateTaskTool,
|
||||
attio_update_webhook: attioUpdateWebhookTool,
|
||||
ahrefs_domain_rating: ahrefsDomainRatingTool,
|
||||
ahrefs_backlinks: ahrefsBacklinksTool,
|
||||
ahrefs_backlinks_stats: ahrefsBacklinksStatsTool,
|
||||
|
||||
40
apps/sim/triggers/attio/comment_created.ts
Normal file
40
apps/sim/triggers/attio/comment_created.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildCommentOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Comment Created Trigger
|
||||
*
|
||||
* Triggers when a comment is created in Attio.
|
||||
*/
|
||||
export const attioCommentCreatedTrigger: TriggerConfig = {
|
||||
id: 'attio_comment_created',
|
||||
name: 'Attio Comment Created',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a new comment is created in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_comment_created',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('comment.created'),
|
||||
extraFields: buildAttioExtraFields('attio_comment_created'),
|
||||
}),
|
||||
|
||||
outputs: buildCommentOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/comment_deleted.ts
Normal file
40
apps/sim/triggers/attio/comment_deleted.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildCommentOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Comment Deleted Trigger
|
||||
*
|
||||
* Triggers when a comment is deleted in Attio.
|
||||
*/
|
||||
export const attioCommentDeletedTrigger: TriggerConfig = {
|
||||
id: 'attio_comment_deleted',
|
||||
name: 'Attio Comment Deleted',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a comment is deleted in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_comment_deleted',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('comment.deleted'),
|
||||
extraFields: buildAttioExtraFields('attio_comment_deleted'),
|
||||
}),
|
||||
|
||||
outputs: buildCommentOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/comment_resolved.ts
Normal file
40
apps/sim/triggers/attio/comment_resolved.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildCommentOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Comment Resolved Trigger
|
||||
*
|
||||
* Triggers when a comment thread is resolved in Attio.
|
||||
*/
|
||||
export const attioCommentResolvedTrigger: TriggerConfig = {
|
||||
id: 'attio_comment_resolved',
|
||||
name: 'Attio Comment Resolved',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a comment thread is resolved in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_comment_resolved',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('comment.resolved'),
|
||||
extraFields: buildAttioExtraFields('attio_comment_resolved'),
|
||||
}),
|
||||
|
||||
outputs: buildCommentOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/comment_unresolved.ts
Normal file
40
apps/sim/triggers/attio/comment_unresolved.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildCommentOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Comment Unresolved Trigger
|
||||
*
|
||||
* Triggers when a comment thread is unresolved in Attio.
|
||||
*/
|
||||
export const attioCommentUnresolvedTrigger: TriggerConfig = {
|
||||
id: 'attio_comment_unresolved',
|
||||
name: 'Attio Comment Unresolved',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a comment thread is unresolved in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_comment_unresolved',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('comment.unresolved'),
|
||||
extraFields: buildAttioExtraFields('attio_comment_unresolved'),
|
||||
}),
|
||||
|
||||
outputs: buildCommentOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
18
apps/sim/triggers/attio/index.ts
Normal file
18
apps/sim/triggers/attio/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export { attioCommentCreatedTrigger } from './comment_created'
|
||||
export { attioCommentDeletedTrigger } from './comment_deleted'
|
||||
export { attioCommentResolvedTrigger } from './comment_resolved'
|
||||
export { attioCommentUnresolvedTrigger } from './comment_unresolved'
|
||||
export { attioListEntryCreatedTrigger } from './list_entry_created'
|
||||
export { attioListEntryDeletedTrigger } from './list_entry_deleted'
|
||||
export { attioListEntryUpdatedTrigger } from './list_entry_updated'
|
||||
export { attioNoteCreatedTrigger } from './note_created'
|
||||
export { attioNoteDeletedTrigger } from './note_deleted'
|
||||
export { attioNoteUpdatedTrigger } from './note_updated'
|
||||
export { attioRecordCreatedTrigger } from './record_created'
|
||||
export { attioRecordDeletedTrigger } from './record_deleted'
|
||||
export { attioRecordMergedTrigger } from './record_merged'
|
||||
export { attioRecordUpdatedTrigger } from './record_updated'
|
||||
export { attioTaskCreatedTrigger } from './task_created'
|
||||
export { attioTaskDeletedTrigger } from './task_deleted'
|
||||
export { attioTaskUpdatedTrigger } from './task_updated'
|
||||
export { attioWebhookTrigger } from './webhook'
|
||||
40
apps/sim/triggers/attio/list_entry_created.ts
Normal file
40
apps/sim/triggers/attio/list_entry_created.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildListEntryOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio List Entry Created Trigger
|
||||
*
|
||||
* Triggers when a list entry is created in Attio.
|
||||
*/
|
||||
export const attioListEntryCreatedTrigger: TriggerConfig = {
|
||||
id: 'attio_list_entry_created',
|
||||
name: 'Attio List Entry Created',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a new list entry is created in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_list_entry_created',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('list-entry.created'),
|
||||
extraFields: buildAttioExtraFields('attio_list_entry_created'),
|
||||
}),
|
||||
|
||||
outputs: buildListEntryOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/list_entry_deleted.ts
Normal file
40
apps/sim/triggers/attio/list_entry_deleted.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildListEntryOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio List Entry Deleted Trigger
|
||||
*
|
||||
* Triggers when a list entry is deleted in Attio.
|
||||
*/
|
||||
export const attioListEntryDeletedTrigger: TriggerConfig = {
|
||||
id: 'attio_list_entry_deleted',
|
||||
name: 'Attio List Entry Deleted',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a list entry is deleted in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_list_entry_deleted',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('list-entry.deleted'),
|
||||
extraFields: buildAttioExtraFields('attio_list_entry_deleted'),
|
||||
}),
|
||||
|
||||
outputs: buildListEntryOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/list_entry_updated.ts
Normal file
40
apps/sim/triggers/attio/list_entry_updated.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildListEntryUpdatedOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio List Entry Updated Trigger
|
||||
*
|
||||
* Triggers when a list entry is updated in Attio.
|
||||
*/
|
||||
export const attioListEntryUpdatedTrigger: TriggerConfig = {
|
||||
id: 'attio_list_entry_updated',
|
||||
name: 'Attio List Entry Updated',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a list entry is updated in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_list_entry_updated',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('list-entry.updated'),
|
||||
extraFields: buildAttioExtraFields('attio_list_entry_updated'),
|
||||
}),
|
||||
|
||||
outputs: buildListEntryUpdatedOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/note_created.ts
Normal file
40
apps/sim/triggers/attio/note_created.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildNoteOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Note Created Trigger
|
||||
*
|
||||
* Triggers when a note is created in Attio.
|
||||
*/
|
||||
export const attioNoteCreatedTrigger: TriggerConfig = {
|
||||
id: 'attio_note_created',
|
||||
name: 'Attio Note Created',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a new note is created in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_note_created',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('note.created'),
|
||||
extraFields: buildAttioExtraFields('attio_note_created'),
|
||||
}),
|
||||
|
||||
outputs: buildNoteOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/note_deleted.ts
Normal file
40
apps/sim/triggers/attio/note_deleted.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildNoteOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Note Deleted Trigger
|
||||
*
|
||||
* Triggers when a note is deleted in Attio.
|
||||
*/
|
||||
export const attioNoteDeletedTrigger: TriggerConfig = {
|
||||
id: 'attio_note_deleted',
|
||||
name: 'Attio Note Deleted',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a note is deleted in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_note_deleted',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('note.deleted'),
|
||||
extraFields: buildAttioExtraFields('attio_note_deleted'),
|
||||
}),
|
||||
|
||||
outputs: buildNoteOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/note_updated.ts
Normal file
40
apps/sim/triggers/attio/note_updated.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildNoteOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Note Updated Trigger
|
||||
*
|
||||
* Triggers when a note is updated in Attio.
|
||||
*/
|
||||
export const attioNoteUpdatedTrigger: TriggerConfig = {
|
||||
id: 'attio_note_updated',
|
||||
name: 'Attio Note Updated',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a note is updated in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_note_updated',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('note.updated'),
|
||||
extraFields: buildAttioExtraFields('attio_note_updated'),
|
||||
}),
|
||||
|
||||
outputs: buildNoteOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
42
apps/sim/triggers/attio/record_created.ts
Normal file
42
apps/sim/triggers/attio/record_created.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildRecordOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Record Created Trigger
|
||||
*
|
||||
* This is the PRIMARY trigger - it includes the dropdown for selecting trigger type.
|
||||
* Triggers when a new record is created in Attio.
|
||||
*/
|
||||
export const attioRecordCreatedTrigger: TriggerConfig = {
|
||||
id: 'attio_record_created',
|
||||
name: 'Attio Record Created',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a new record is created in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_record_created',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
includeDropdown: true,
|
||||
setupInstructions: attioSetupInstructions('record.created'),
|
||||
extraFields: buildAttioExtraFields('attio_record_created'),
|
||||
}),
|
||||
|
||||
outputs: buildRecordOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/record_deleted.ts
Normal file
40
apps/sim/triggers/attio/record_deleted.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildRecordOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Record Deleted Trigger
|
||||
*
|
||||
* Triggers when a record is deleted in Attio.
|
||||
*/
|
||||
export const attioRecordDeletedTrigger: TriggerConfig = {
|
||||
id: 'attio_record_deleted',
|
||||
name: 'Attio Record Deleted',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a record is deleted in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_record_deleted',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('record.deleted'),
|
||||
extraFields: buildAttioExtraFields('attio_record_deleted'),
|
||||
}),
|
||||
|
||||
outputs: buildRecordOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/record_merged.ts
Normal file
40
apps/sim/triggers/attio/record_merged.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildRecordMergedEventOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Record Merged Trigger
|
||||
*
|
||||
* Triggers when two records are merged in Attio.
|
||||
*/
|
||||
export const attioRecordMergedTrigger: TriggerConfig = {
|
||||
id: 'attio_record_merged',
|
||||
name: 'Attio Record Merged',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when two records are merged in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_record_merged',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('record.merged'),
|
||||
extraFields: buildAttioExtraFields('attio_record_merged'),
|
||||
}),
|
||||
|
||||
outputs: buildRecordMergedEventOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/record_updated.ts
Normal file
40
apps/sim/triggers/attio/record_updated.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildRecordUpdatedOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Record Updated Trigger
|
||||
*
|
||||
* Triggers when a record is updated in Attio.
|
||||
*/
|
||||
export const attioRecordUpdatedTrigger: TriggerConfig = {
|
||||
id: 'attio_record_updated',
|
||||
name: 'Attio Record Updated',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a record is updated in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_record_updated',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('record.updated'),
|
||||
extraFields: buildAttioExtraFields('attio_record_updated'),
|
||||
}),
|
||||
|
||||
outputs: buildRecordUpdatedOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/task_created.ts
Normal file
40
apps/sim/triggers/attio/task_created.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildTaskOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Task Created Trigger
|
||||
*
|
||||
* Triggers when a task is created in Attio.
|
||||
*/
|
||||
export const attioTaskCreatedTrigger: TriggerConfig = {
|
||||
id: 'attio_task_created',
|
||||
name: 'Attio Task Created',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a new task is created in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_task_created',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('task.created'),
|
||||
extraFields: buildAttioExtraFields('attio_task_created'),
|
||||
}),
|
||||
|
||||
outputs: buildTaskOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/task_deleted.ts
Normal file
40
apps/sim/triggers/attio/task_deleted.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildTaskOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Task Deleted Trigger
|
||||
*
|
||||
* Triggers when a task is deleted in Attio.
|
||||
*/
|
||||
export const attioTaskDeletedTrigger: TriggerConfig = {
|
||||
id: 'attio_task_deleted',
|
||||
name: 'Attio Task Deleted',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a task is deleted in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_task_deleted',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('task.deleted'),
|
||||
extraFields: buildAttioExtraFields('attio_task_deleted'),
|
||||
}),
|
||||
|
||||
outputs: buildTaskOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
40
apps/sim/triggers/attio/task_updated.ts
Normal file
40
apps/sim/triggers/attio/task_updated.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildTaskOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Attio Task Updated Trigger
|
||||
*
|
||||
* Triggers when a task is updated in Attio.
|
||||
*/
|
||||
export const attioTaskUpdatedTrigger: TriggerConfig = {
|
||||
id: 'attio_task_updated',
|
||||
name: 'Attio Task Updated',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow when a task is updated in Attio',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_task_updated',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('task.updated'),
|
||||
extraFields: buildAttioExtraFields('attio_task_updated'),
|
||||
}),
|
||||
|
||||
outputs: buildTaskOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
295
apps/sim/triggers/attio/utils.ts
Normal file
295
apps/sim/triggers/attio/utils.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import type { TriggerOutput } from '@/triggers/types'
|
||||
|
||||
export const attioTriggerOptions = [
|
||||
{ label: 'Record Created', id: 'attio_record_created' },
|
||||
{ label: 'Record Updated', id: 'attio_record_updated' },
|
||||
{ label: 'Record Deleted', id: 'attio_record_deleted' },
|
||||
{ label: 'Record Merged', id: 'attio_record_merged' },
|
||||
{ label: 'Note Created', id: 'attio_note_created' },
|
||||
{ label: 'Note Updated', id: 'attio_note_updated' },
|
||||
{ label: 'Note Deleted', id: 'attio_note_deleted' },
|
||||
{ label: 'Task Created', id: 'attio_task_created' },
|
||||
{ label: 'Task Updated', id: 'attio_task_updated' },
|
||||
{ label: 'Task Deleted', id: 'attio_task_deleted' },
|
||||
{ label: 'Comment Created', id: 'attio_comment_created' },
|
||||
{ label: 'Comment Resolved', id: 'attio_comment_resolved' },
|
||||
{ label: 'Comment Unresolved', id: 'attio_comment_unresolved' },
|
||||
{ label: 'Comment Deleted', id: 'attio_comment_deleted' },
|
||||
{ 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: 'Generic Webhook (All Events)', id: 'attio_webhook' },
|
||||
]
|
||||
|
||||
export function attioSetupInstructions(eventType: string): string {
|
||||
const instructions = [
|
||||
'<strong>Note:</strong> You need access to the Attio developer settings to create webhooks. See the <a href="https://docs.attio.com/rest-api/guides/webhooks" target="_blank" rel="noopener noreferrer">Attio webhook documentation</a> for details.',
|
||||
'In Attio, navigate to <strong>Settings > Developers</strong> and select your integration.',
|
||||
'Go to the <strong>Webhooks</strong> tab and click <strong>"Create Webhook"</strong>.',
|
||||
'Paste the <strong>Webhook URL</strong> from above into the target URL field.',
|
||||
`Add a subscription with the event type <strong>${eventType}</strong>. You can optionally add filters to scope the events.`,
|
||||
'Save the webhook. Copy the <strong>signing secret</strong> shown and paste it in the field above for signature verification.',
|
||||
'The webhook is now active. Attio will send events to the URL you configured.',
|
||||
]
|
||||
|
||||
return instructions
|
||||
.map(
|
||||
(instruction, index) =>
|
||||
`<div class="mb-3">${index === 0 ? instruction : `<strong>${index}.</strong> ${instruction}`}</div>`
|
||||
)
|
||||
.join('')
|
||||
}
|
||||
|
||||
export function buildAttioExtraFields(triggerId: string): SubBlockConfig[] {
|
||||
return [
|
||||
{
|
||||
id: 'webhookSecret',
|
||||
title: 'Webhook Secret',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter the webhook signing secret from Attio',
|
||||
description:
|
||||
'The signing secret from Attio used to verify webhook deliveries via HMAC-SHA256 signature',
|
||||
password: true,
|
||||
required: false,
|
||||
mode: 'trigger',
|
||||
condition: { field: 'selectedTriggerId', value: triggerId },
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Base webhook outputs common to all Attio triggers.
|
||||
*/
|
||||
function buildBaseWebhookOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
eventType: {
|
||||
type: 'string',
|
||||
description: 'The type of event (e.g. record.created, note.created)',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record event outputs for record triggers.
|
||||
*/
|
||||
function buildRecordIdOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
workspaceId: { type: 'string', description: 'The workspace ID' },
|
||||
objectId: { type: 'string', description: 'The object type ID (e.g. people, companies)' },
|
||||
recordId: { type: 'string', description: 'The record ID' },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record updated event outputs (includes attributeId).
|
||||
*/
|
||||
function buildRecordUpdatedIdOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildRecordIdOutputs(),
|
||||
attributeId: {
|
||||
type: 'string',
|
||||
description: 'The ID of the attribute that was updated on the record',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record merged event outputs.
|
||||
* Attio payload: id.record_id (winner), duplicate_object_id, duplicate_record_id (loser).
|
||||
*/
|
||||
function buildRecordMergedOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
workspaceId: { type: 'string', description: 'The workspace ID' },
|
||||
objectId: { type: 'string', description: 'The object type ID of the surviving record' },
|
||||
recordId: { type: 'string', description: 'The surviving record ID after merge' },
|
||||
duplicateObjectId: {
|
||||
type: 'string',
|
||||
description: 'The object type ID of the merged-away record',
|
||||
},
|
||||
duplicateRecordId: { type: 'string', description: 'The record ID that was merged away' },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note event outputs.
|
||||
*/
|
||||
function buildNoteIdOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
workspaceId: { type: 'string', description: 'The workspace ID' },
|
||||
noteId: { type: 'string', description: 'The note ID' },
|
||||
parentObjectId: { type: 'string', description: 'The parent object type ID' },
|
||||
parentRecordId: { type: 'string', description: 'The parent record ID' },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task event outputs.
|
||||
* Attio task webhook payloads only contain workspace_id and task_id.
|
||||
*/
|
||||
function buildTaskIdOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
workspaceId: { type: 'string', description: 'The workspace ID' },
|
||||
taskId: { type: 'string', description: 'The task ID' },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comment event outputs.
|
||||
* Attio payload uses object_id/record_id (not parent_*), plus list_id/entry_id.
|
||||
*/
|
||||
function buildCommentIdOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
workspaceId: { type: 'string', description: 'The workspace ID' },
|
||||
threadId: { type: 'string', description: 'The thread ID' },
|
||||
commentId: { type: 'string', description: 'The comment ID' },
|
||||
objectId: { type: 'string', description: 'The object type ID' },
|
||||
recordId: { type: 'string', description: 'The record ID' },
|
||||
listId: { type: 'string', description: 'The list ID (if comment is on a list entry)' },
|
||||
entryId: { type: 'string', description: 'The list entry ID (if comment is on a list entry)' },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List entry event outputs.
|
||||
*/
|
||||
function buildListEntryIdOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
workspaceId: { type: 'string', description: 'The workspace ID' },
|
||||
listId: { type: 'string', description: 'The list ID' },
|
||||
entryId: { type: 'string', description: 'The list entry ID' },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List entry updated event outputs (includes attributeId).
|
||||
*/
|
||||
function buildListEntryUpdatedIdOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildListEntryIdOutputs(),
|
||||
attributeId: {
|
||||
type: 'string',
|
||||
description: 'The ID of the attribute that was updated on the list entry',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Record created/deleted outputs. */
|
||||
export function buildRecordOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
...buildRecordIdOutputs(),
|
||||
}
|
||||
}
|
||||
|
||||
/** Record updated outputs (includes attributeId). */
|
||||
export function buildRecordUpdatedOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
...buildRecordUpdatedIdOutputs(),
|
||||
}
|
||||
}
|
||||
|
||||
/** Record merged outputs. */
|
||||
export function buildRecordMergedEventOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
...buildRecordMergedOutputs(),
|
||||
}
|
||||
}
|
||||
|
||||
/** Note event outputs. */
|
||||
export function buildNoteOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
...buildNoteIdOutputs(),
|
||||
}
|
||||
}
|
||||
|
||||
/** Task event outputs. */
|
||||
export function buildTaskOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
...buildTaskIdOutputs(),
|
||||
}
|
||||
}
|
||||
|
||||
/** Comment event outputs. */
|
||||
export function buildCommentOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
...buildCommentIdOutputs(),
|
||||
}
|
||||
}
|
||||
|
||||
/** List entry created/deleted outputs. */
|
||||
export function buildListEntryOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
...buildListEntryIdOutputs(),
|
||||
}
|
||||
}
|
||||
|
||||
/** List entry updated outputs (includes attributeId). */
|
||||
export function buildListEntryUpdatedOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
...buildListEntryUpdatedIdOutputs(),
|
||||
}
|
||||
}
|
||||
|
||||
/** Generic webhook outputs covering all event types. */
|
||||
export function buildGenericWebhookOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
id: { type: 'json', description: 'The event ID object containing resource identifiers' },
|
||||
parentObjectId: {
|
||||
type: 'string',
|
||||
description: 'The parent object type ID (if applicable)',
|
||||
},
|
||||
parentRecordId: {
|
||||
type: 'string',
|
||||
description: 'The parent record ID (if applicable)',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps trigger IDs to the exact Attio event type strings.
|
||||
*/
|
||||
const TRIGGER_EVENT_MAP: Record<string, string[]> = {
|
||||
attio_record_created: ['record.created'],
|
||||
attio_record_updated: ['record.updated'],
|
||||
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_deleted: ['note.deleted'],
|
||||
attio_task_created: ['task.created'],
|
||||
attio_task_updated: ['task.updated'],
|
||||
attio_task_deleted: ['task.deleted'],
|
||||
attio_comment_created: ['comment.created'],
|
||||
attio_comment_resolved: ['comment.resolved'],
|
||||
attio_comment_unresolved: ['comment.unresolved'],
|
||||
attio_comment_deleted: ['comment.deleted'],
|
||||
attio_list_entry_created: ['list-entry.created'],
|
||||
attio_list_entry_updated: ['list-entry.updated'],
|
||||
attio_list_entry_deleted: ['list-entry.deleted'],
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an Attio webhook payload matches a trigger.
|
||||
*/
|
||||
export function isAttioPayloadMatch(triggerId: string, body: Record<string, unknown>): boolean {
|
||||
if (triggerId === 'attio_webhook') {
|
||||
return true
|
||||
}
|
||||
|
||||
const eventType = body.event_type as string | undefined
|
||||
if (!eventType) {
|
||||
return false
|
||||
}
|
||||
|
||||
const acceptedEvents = TRIGGER_EVENT_MAP[triggerId]
|
||||
return acceptedEvents ? acceptedEvents.includes(eventType) : false
|
||||
}
|
||||
40
apps/sim/triggers/attio/webhook.ts
Normal file
40
apps/sim/triggers/attio/webhook.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AttioIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
attioSetupInstructions,
|
||||
attioTriggerOptions,
|
||||
buildAttioExtraFields,
|
||||
buildGenericWebhookOutputs,
|
||||
} from '@/triggers/attio/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Generic Attio Webhook Trigger
|
||||
*
|
||||
* Captures all Attio webhook events without filtering.
|
||||
*/
|
||||
export const attioWebhookTrigger: TriggerConfig = {
|
||||
id: 'attio_webhook',
|
||||
name: 'Attio Webhook (All Events)',
|
||||
provider: 'attio',
|
||||
description: 'Trigger workflow on any Attio webhook event',
|
||||
version: '1.0.0',
|
||||
icon: AttioIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'attio_webhook',
|
||||
triggerOptions: attioTriggerOptions,
|
||||
setupInstructions: attioSetupInstructions('All Events'),
|
||||
extraFields: buildAttioExtraFields('attio_webhook'),
|
||||
}),
|
||||
|
||||
outputs: buildGenericWebhookOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Attio-Signature': 'hmac-sha256-signature',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,24 @@
|
||||
import { airtableWebhookTrigger } from '@/triggers/airtable'
|
||||
import {
|
||||
attioCommentCreatedTrigger,
|
||||
attioCommentDeletedTrigger,
|
||||
attioCommentResolvedTrigger,
|
||||
attioCommentUnresolvedTrigger,
|
||||
attioListEntryCreatedTrigger,
|
||||
attioListEntryDeletedTrigger,
|
||||
attioListEntryUpdatedTrigger,
|
||||
attioNoteCreatedTrigger,
|
||||
attioNoteDeletedTrigger,
|
||||
attioNoteUpdatedTrigger,
|
||||
attioRecordCreatedTrigger,
|
||||
attioRecordDeletedTrigger,
|
||||
attioRecordMergedTrigger,
|
||||
attioRecordUpdatedTrigger,
|
||||
attioTaskCreatedTrigger,
|
||||
attioTaskDeletedTrigger,
|
||||
attioTaskUpdatedTrigger,
|
||||
attioWebhookTrigger,
|
||||
} from '@/triggers/attio'
|
||||
import {
|
||||
calcomBookingCancelledTrigger,
|
||||
calcomBookingCreatedTrigger,
|
||||
@@ -145,6 +165,24 @@ import { whatsappWebhookTrigger } from '@/triggers/whatsapp'
|
||||
export const TRIGGER_REGISTRY: TriggerRegistry = {
|
||||
slack_webhook: slackWebhookTrigger,
|
||||
airtable_webhook: airtableWebhookTrigger,
|
||||
attio_webhook: attioWebhookTrigger,
|
||||
attio_record_created: attioRecordCreatedTrigger,
|
||||
attio_record_updated: attioRecordUpdatedTrigger,
|
||||
attio_record_deleted: attioRecordDeletedTrigger,
|
||||
attio_record_merged: attioRecordMergedTrigger,
|
||||
attio_note_created: attioNoteCreatedTrigger,
|
||||
attio_note_updated: attioNoteUpdatedTrigger,
|
||||
attio_note_deleted: attioNoteDeletedTrigger,
|
||||
attio_task_created: attioTaskCreatedTrigger,
|
||||
attio_task_updated: attioTaskUpdatedTrigger,
|
||||
attio_task_deleted: attioTaskDeletedTrigger,
|
||||
attio_comment_created: attioCommentCreatedTrigger,
|
||||
attio_comment_resolved: attioCommentResolvedTrigger,
|
||||
attio_comment_unresolved: attioCommentUnresolvedTrigger,
|
||||
attio_comment_deleted: attioCommentDeletedTrigger,
|
||||
attio_list_entry_created: attioListEntryCreatedTrigger,
|
||||
attio_list_entry_updated: attioListEntryUpdatedTrigger,
|
||||
attio_list_entry_deleted: attioListEntryDeletedTrigger,
|
||||
calendly_webhook: calendlyWebhookTrigger,
|
||||
calendly_invitee_created: calendlyInviteeCreatedTrigger,
|
||||
calendly_invitee_canceled: calendlyInviteeCanceledTrigger,
|
||||
|
||||
Reference in New Issue
Block a user