Skip to content
Merged
722 changes: 648 additions & 74 deletions apps/sim/blocks/blocks/attio.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/sim/tools/attio/assert_record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ export const attioAssertRecordTool: ToolConfig<AttioAssertRecordParams, AttioAss
'Content-Type': 'application/json',
}),
body: (params) => {
let values: Record<string, unknown> = {}
let values: Record<string, unknown>
try {
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
} catch {
values = {}
throw new Error('Invalid JSON provided for record values')
}
return { data: { values } }
},
Expand Down
157 changes: 157 additions & 0 deletions apps/sim/tools/attio/create_attribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { AttioCreateAttributeParams, AttioCreateAttributeResponse } from './types'
import { ATTRIBUTE_OUTPUT_PROPERTIES } from './types'

const logger = createLogger('AttioCreateAttribute')

export const attioCreateAttributeTool: ToolConfig<
AttioCreateAttributeParams,
AttioCreateAttributeResponse
> = {
id: 'attio_create_attribute',
name: 'Attio Create Attribute',
description: 'Create a new attribute (schema field) on an Attio object or 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',
},
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Whether to create the attribute on an object or a list: objects or lists',
},
identifier: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The object or list ID or slug (e.g. people, companies)',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The attribute display title',
},
apiSlug: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The attribute API slug (unique, snake_case)',
},
type: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'The attribute value type (e.g. text, number, checkbox, currency, date, timestamp, rating, status, select, record-reference, actor-reference, location, domain, email-address, phone-number)',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'A description of the attribute',
},
isRequired: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether new records must provide a value (default false)',
},
isUnique: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the attribute enforces uniqueness on new data (default false)',
},
isMultiselect: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the attribute supports multiple values (default false)',
},
config: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON object of type-dependent configuration (e.g. currency or record-reference settings)',
},
},

request: {
url: (params) =>
`https://api.attio.com/v2/${params.target.trim()}/${params.identifier.trim()}/attributes`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const data: Record<string, unknown> = {
title: params.title,
api_slug: params.apiSlug,
description: params.description ?? null,
type: params.type,
is_required: params.isRequired ?? false,
is_unique: params.isUnique ?? false,
is_multiselect: params.isMultiselect ?? false,
// `config` is a required key on Attio's create-attribute request body (even though its
// nested fields are only required for type-dependent configs like currency/record-reference).
config: {},
}
if (params.config) {
try {
data.config =
typeof params.config === 'string' ? JSON.parse(params.config) : params.config
} catch {
throw new Error('Invalid JSON provided for attribute config')
}
}
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 attribute')
}
const attr = data.data
return {
success: true,
output: {
attributeId: attr.id?.attribute_id ?? null,
title: attr.title ?? null,
apiSlug: attr.api_slug ?? null,
description: attr.description ?? null,
type: attr.type ?? null,
isSystemAttribute: attr.is_system_attribute ?? false,
isWritable: attr.is_writable ?? false,
isRequired: attr.is_required ?? false,
isUnique: attr.is_unique ?? false,
isMultiselect: attr.is_multiselect ?? false,
isDefaultValueEnabled: attr.is_default_value_enabled ?? false,
isArchived: attr.is_archived ?? false,
defaultValue: attr.default_value ?? null,
relationship: attr.relationship ?? null,
config: attr.config ?? null,
createdAt: attr.created_at ?? null,
},
}
},

outputs: ATTRIBUTE_OUTPUT_PROPERTIES,
}
45 changes: 37 additions & 8 deletions apps/sim/tools/attio/create_comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,37 @@ export const attioCreateCommentTool: ToolConfig<
},
list: {
type: 'string',
required: true,
required: false,
visibility: 'user-or-llm',
description: 'The list ID or slug the entry belongs to',
description:
'The list ID or slug the entry belongs to (used with entryId; omit if threadId or recordId is set)',
},
entryId: {
type: 'string',
required: true,
required: false,
visibility: 'user-or-llm',
description:
'The list entry ID to comment on (used with list; omit if threadId or recordId is set)',
},
recordObject: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The entry ID to comment on',
description:
'The object ID or slug the record belongs to (used with recordId; omit if threadId or entryId is set)',
},
recordId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'The record ID to comment on directly (used with recordObject; omit if threadId or entryId is set)',
},
threadId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Thread ID to reply to (omit to start a new thread)',
description: 'Thread ID to reply to (omit to start a new thread on a record or list entry)',
},
createdAt: {
type: 'string',
Expand All @@ -91,12 +107,25 @@ export const attioCreateCommentTool: ToolConfig<
type: params.authorType,
id: params.authorId,
},
entry: {
}
// Attio's comment body accepts exactly one of `thread_id`, `record`, or `entry` — mutually exclusive.
if (params.threadId) {
data.thread_id = params.threadId
} else if (params.recordObject && params.recordId) {
data.record = {
object: params.recordObject,
record_id: params.recordId,
}
} else if (params.list && params.entryId) {
data.entry = {
list: params.list,
entry_id: params.entryId,
},
}
Comment thread
waleedlatif1 marked this conversation as resolved.
} else {
throw new Error(
'Must provide either threadId, both recordObject and recordId, or both list and entryId'
)
}
if (params.threadId) data.thread_id = params.threadId
if (params.createdAt) data.created_at = params.createdAt
return { data }
},
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/tools/attio/create_note.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { AttioCreateNoteParams, AttioCreateNoteResponse } from './types'
import { NOTE_OUTPUT_PROPERTIES } from './types'
import { mapNoteTags, NOTE_OUTPUT_PROPERTIES } from './types'

const logger = createLogger('AttioCreateNote')

Expand Down Expand Up @@ -105,7 +105,7 @@ export const attioCreateNoteTool: ToolConfig<AttioCreateNoteParams, AttioCreateN
contentPlaintext: note.content_plaintext ?? null,
contentMarkdown: note.content_markdown ?? null,
meetingId: note.meeting_id ?? null,
tags: note.tags ?? [],
tags: mapNoteTags(note.tags),
createdByActor: note.created_by_actor ?? null,
createdAt: note.created_at ?? null,
},
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/attio/create_record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const attioCreateRecordTool: ToolConfig<AttioCreateRecordParams, AttioCre
try {
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
} catch {
values = {}
throw new Error('Invalid JSON provided for record values')
}
return { data: { values } }
},
Expand Down
1 change: 1 addition & 0 deletions apps/sim/tools/attio/create_task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export const attioCreateTaskTool: ToolConfig<AttioCreateTaskParams, AttioCreateT
content: task.content_plaintext ?? null,
deadlineAt: task.deadline_at ?? null,
isCompleted: task.is_completed ?? false,
completedAt: task.completed_at ?? null,
linkedRecords,
assignees,
createdByActor: task.created_by_actor ?? null,
Expand Down
87 changes: 87 additions & 0 deletions apps/sim/tools/attio/get_attribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { AttioGetAttributeParams, AttioGetAttributeResponse } from './types'
import { ATTRIBUTE_OUTPUT_PROPERTIES } from './types'

const logger = createLogger('AttioGetAttribute')

export const attioGetAttributeTool: ToolConfig<AttioGetAttributeParams, AttioGetAttributeResponse> =
{
id: 'attio_get_attribute',
name: 'Attio Get Attribute',
description: 'Get a single attribute (schema field) on an Attio object or 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',
},
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Whether the attribute belongs to an object or a list: objects or lists',
},
identifier: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The object or list ID or slug (e.g. people, companies)',
},
attribute: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The attribute ID or slug',
},
},

request: {
url: (params) =>
`https://api.attio.com/v2/${params.target.trim()}/${params.identifier.trim()}/attributes/${params.attribute.trim()}`,
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 attribute')
}
const attr = data.data
return {
success: true,
output: {
attributeId: attr.id?.attribute_id ?? null,
title: attr.title ?? null,
apiSlug: attr.api_slug ?? null,
description: attr.description ?? null,
type: attr.type ?? null,
isSystemAttribute: attr.is_system_attribute ?? false,
isWritable: attr.is_writable ?? false,
isRequired: attr.is_required ?? false,
isUnique: attr.is_unique ?? false,
isMultiselect: attr.is_multiselect ?? false,
isDefaultValueEnabled: attr.is_default_value_enabled ?? false,
isArchived: attr.is_archived ?? false,
defaultValue: attr.default_value ?? null,
relationship: attr.relationship ?? null,
config: attr.config ?? null,
createdAt: attr.created_at ?? null,
},
}
},

outputs: ATTRIBUTE_OUTPUT_PROPERTIES,
}
4 changes: 2 additions & 2 deletions apps/sim/tools/attio/get_note.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { AttioGetNoteParams, AttioGetNoteResponse } from './types'
import { NOTE_OUTPUT_PROPERTIES } from './types'
import { mapNoteTags, NOTE_OUTPUT_PROPERTIES } from './types'

const logger = createLogger('AttioGetNote')

Expand Down Expand Up @@ -56,7 +56,7 @@ export const attioGetNoteTool: ToolConfig<AttioGetNoteParams, AttioGetNoteRespon
contentPlaintext: note.content_plaintext ?? null,
contentMarkdown: note.content_markdown ?? null,
meetingId: note.meeting_id ?? null,
tags: note.tags ?? [],
tags: mapNoteTags(note.tags),
createdByActor: note.created_by_actor ?? null,
createdAt: note.created_at ?? null,
},
Expand Down
Loading
Loading