chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+95
View 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.trim()}/records?matching_attribute=${params.matchingAttribute.trim()}`,
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 {
throw new Error('Invalid JSON provided for record 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' },
},
}
+157
View File
@@ -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,
}
+166
View File
@@ -0,0 +1,166 @@
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: false,
visibility: 'user-or-llm',
description:
'The list ID or slug the entry belongs to (used with entryId; omit if threadId or recordId is set)',
},
entryId: {
type: 'string',
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 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 on a record or list entry)',
},
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,
},
}
// 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,
}
} else {
throw new Error(
'Must provide either threadId, both recordObject and recordId, or both list and entryId'
)
}
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,
}
+121
View File
@@ -0,0 +1,121 @@
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 apiSlug =
params.apiSlug ||
params.name
?.toLowerCase()
.replace(/[^a-z0-9]+/g, '_')
.replace(/^_|_$/g, '')
const data: Record<string, unknown> = {
name: params.name,
api_slug: apiSlug,
parent_object: params.parentObject,
workspace_access: params.workspaceAccess ?? null,
workspace_member_access: [] as unknown[],
}
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,
}
+104
View File
@@ -0,0 +1,104 @@
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.trim()}/entries`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
let entryValues: unknown = {}
if (params.entryValues) {
try {
entryValues =
typeof params.entryValues === 'string'
? JSON.parse(params.entryValues)
: params.entryValues
} catch {
entryValues = {}
}
}
const data: Record<string, unknown> = {
parent_record_id: params.parentRecordId,
parent_object: params.parentObject,
entry_values: entryValues,
}
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
View File
@@ -0,0 +1,116 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { AttioCreateNoteParams, AttioCreateNoteResponse } from './types'
import { mapNoteTags, 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 != null) 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: mapNoteTags(note.tags),
createdByActor: note.created_by_actor ?? null,
createdAt: note.created_at ?? null,
},
}
},
outputs: NOTE_OUTPUT_PROPERTIES,
}
+83
View 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
View 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.trim()}/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 {
throw new Error('Invalid JSON provided for record 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' },
},
}
+137
View File
@@ -0,0 +1,137 @@
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,
completedAt: task.completed_at ?? null,
linkedRecords,
assignees,
createdByActor: task.created_by_actor ?? null,
createdAt: task.created_at ?? null,
},
}
},
outputs: TASK_OUTPUT_PROPERTIES,
}
+104
View 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
View 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.trim()}`,
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' },
},
}
+68
View File
@@ -0,0 +1,68 @@
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.trim()}/entries/${params.entryId.trim()}`,
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
View 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.trim()}`,
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
View 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.trim()}/records/${params.recordId.trim()}`,
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
View 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.trim()}`,
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
View 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.trim()}`,
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' },
},
}
+87
View File
@@ -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,
}
+74
View 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.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 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
View 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.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 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,
}
+71
View File
@@ -0,0 +1,71 @@
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.trim()}/entries/${params.entryId.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 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
View 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.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 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
View File
@@ -0,0 +1,67 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { AttioGetNoteParams, AttioGetNoteResponse } from './types'
import { mapNoteTags, 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.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 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: mapNoteTags(note.tags),
createdByActor: note.created_by_actor ?? null,
createdAt: note.created_at ?? null,
},
}
},
outputs: NOTE_OUTPUT_PROPERTIES,
}
+62
View 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.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 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
View 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.trim()}/records/${params.recordId.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 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' },
},
}
+78
View File
@@ -0,0 +1,78 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { AttioGetTaskParams, AttioGetTaskResponse } from './types'
import { TASK_OUTPUT_PROPERTIES } from './types'
const logger = createLogger('AttioGetTask')
export const attioGetTaskTool: ToolConfig<AttioGetTaskParams, AttioGetTaskResponse> = {
id: 'attio_get_task',
name: 'Attio Get Task',
description: 'Get a single task by ID from Attio',
version: '1.0.0',
oauth: {
required: true,
provider: 'attio',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The OAuth access token for the Attio API',
},
taskId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the task to retrieve',
},
},
request: {
url: (params) => `https://api.attio.com/v2/tasks/${params.taskId.trim()}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
logger.error('Attio API request failed', { data, status: response.status })
throw new Error(data.message || 'Failed to get task')
}
const task = data.data
const linkedRecords = (task.linked_records ?? []).map(
(r: { target_object_id?: string; target_record_id?: string }) => ({
targetObjectId: r.target_object_id ?? null,
targetRecordId: r.target_record_id ?? null,
})
)
const assignees = (task.assignees ?? []).map(
(a: { referenced_actor_type?: string; referenced_actor_id?: string }) => ({
type: a.referenced_actor_type ?? null,
id: a.referenced_actor_id ?? null,
})
)
return {
success: true,
output: {
taskId: task.id?.task_id ?? null,
content: task.content_plaintext ?? null,
deadlineAt: task.deadline_at ?? null,
isCompleted: task.is_completed ?? false,
completedAt: task.completed_at ?? null,
linkedRecords,
assignees,
createdByActor: task.created_by_actor ?? null,
createdAt: task.created_at ?? null,
},
}
},
outputs: TASK_OUTPUT_PROPERTIES,
}
+74
View 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.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 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
View 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.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 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,
}
+45
View File
@@ -0,0 +1,45 @@
export { attioAssertRecordTool } from './assert_record'
export { attioCreateAttributeTool } from './create_attribute'
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 { attioGetAttributeTool } from './get_attribute'
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 { attioGetTaskTool } from './get_task'
export { attioGetThreadTool } from './get_thread'
export { attioGetWebhookTool } from './get_webhook'
export { attioListAttributesTool } from './list_attributes'
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 { attioUpdateAttributeTool } from './update_attribute'
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'
+124
View File
@@ -0,0 +1,124 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { AttioListAttributesParams, AttioListAttributesResponse } from './types'
import { ATTRIBUTE_OUTPUT_PROPERTIES } from './types'
const logger = createLogger('AttioListAttributes')
export const attioListAttributesTool: ToolConfig<
AttioListAttributesParams,
AttioListAttributesResponse
> = {
id: 'attio_list_attributes',
name: 'Attio List Attributes',
description: 'List the attributes (schema fields) defined 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 attributes belong 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)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of attributes to return',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of attributes to skip for pagination',
},
showArchived: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to include archived attributes (default false)',
},
},
request: {
url: (params) => {
const searchParams = new URLSearchParams()
if (params.limit != null) searchParams.set('limit', String(params.limit))
if (params.offset != null) searchParams.set('offset', String(params.offset))
if (params.showArchived != null)
searchParams.set('show_archived', String(params.showArchived))
const qs = searchParams.toString()
return `https://api.attio.com/v2/${params.target.trim()}/${params.identifier.trim()}/attributes${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 attributes')
}
const attributes = (data.data ?? []).map((attr: Record<string, unknown>) => {
const id = attr.id as { attribute_id?: string } | undefined
return {
attributeId: id?.attribute_id ?? null,
title: (attr.title as string) ?? null,
apiSlug: (attr.api_slug as string) ?? null,
description: (attr.description as string) ?? null,
type: (attr.type as string) ?? null,
isSystemAttribute: (attr.is_system_attribute as boolean) ?? false,
isWritable: (attr.is_writable as boolean) ?? false,
isRequired: (attr.is_required as boolean) ?? false,
isUnique: (attr.is_unique as boolean) ?? false,
isMultiselect: (attr.is_multiselect as boolean) ?? false,
isDefaultValueEnabled: (attr.is_default_value_enabled as boolean) ?? false,
isArchived: (attr.is_archived as boolean) ?? false,
defaultValue: (attr.default_value as Record<string, unknown>) ?? null,
relationship: (attr.relationship as Record<string, unknown>) ?? null,
config: (attr.config as Record<string, unknown>) ?? null,
createdAt: (attr.created_at as string) ?? null,
}
})
return {
success: true,
output: {
attributes,
count: attributes.length,
},
}
},
outputs: {
attributes: {
type: 'array',
description: 'Array of attributes',
items: {
type: 'object',
properties: ATTRIBUTE_OUTPUT_PROPERTIES,
},
},
count: { type: 'number', description: 'Number of attributes returned' },
},
}
+78
View 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
View 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
View File
@@ -0,0 +1,109 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { AttioListNotesParams, AttioListNotesResponse } from './types'
import { mapNoteTags, 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 != null) searchParams.set('limit', String(params.limit))
if (params.offset != null) 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: mapNoteTags(note.tags),
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
View 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
View 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.trim()}/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 {
throw new Error('Invalid JSON provided for filter')
}
}
if (params.sorts) {
try {
body.sorts = JSON.parse(params.sorts)
} catch {
throw new Error('Invalid JSON provided for sorts')
}
}
if (params.limit != null) body.limit = params.limit
if (params.offset != null) 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' },
},
}
+149
View File
@@ -0,0 +1,149 @@
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, created_at:desc, completed_at:asc, or completed_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 != null) {
searchParams.set('is_completed', String(params.isCompleted))
}
if (params.sort) searchParams.set('sort', params.sort)
if (params.limit != null) searchParams.set('limit', String(params.limit))
if (params.offset != null) 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,
completedAt: (task.completed_at as string) ?? null,
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
View 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 != null) searchParams.set('limit', String(params.limit))
if (params.offset != null) 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
View 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 != null) searchParams.set('limit', String(params.limit))
if (params.offset != null) 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
View 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.trim()}/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 != null) body.limit = params.limit
if (params.offset != null) 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
View 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' },
},
}
File diff suppressed because it is too large Load Diff
+150
View File
@@ -0,0 +1,150 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { AttioUpdateAttributeParams, AttioUpdateAttributeResponse } from './types'
import { ATTRIBUTE_OUTPUT_PROPERTIES } from './types'
const logger = createLogger('AttioUpdateAttribute')
export const attioUpdateAttributeTool: ToolConfig<
AttioUpdateAttributeParams,
AttioUpdateAttributeResponse
> = {
id: 'attio_update_attribute',
name: 'Attio Update Attribute',
description: 'Update an 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 to update',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New attribute display title',
},
apiSlug: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New attribute API slug',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New attribute description',
},
isRequired: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether new records must provide a value',
},
isUnique: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the attribute enforces uniqueness on new data',
},
isArchived: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Archive or unarchive the attribute',
},
config: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON object of type-dependent configuration',
},
},
request: {
url: (params) =>
`https://api.attio.com/v2/${params.target.trim()}/${params.identifier.trim()}/attributes/${params.attribute.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const data: Record<string, unknown> = {}
if (params.title != null) data.title = params.title
if (params.apiSlug != null) data.api_slug = params.apiSlug
if (params.description != null) data.description = params.description
if (params.isRequired != null) data.is_required = params.isRequired
if (params.isUnique != null) data.is_unique = params.isUnique
if (params.isArchived != null) data.is_archived = params.isArchived
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 update 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,
}
+112
View 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.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const data: Record<string, unknown> = {}
if (params.name != null) data.name = params.name
if (params.apiSlug != null) data.api_slug = params.apiSlug
if (params.workspaceAccess != null) data.workspace_access = params.workspaceAccess
if (params.workspaceMemberAccess != null) {
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,
}
+92
View File
@@ -0,0 +1,92 @@
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.trim()}/entries/${params.entryId.trim()}`,
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
View 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.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const data: Record<string, unknown> = {}
if (params.apiSlug != null) data.api_slug = params.apiSlug
if (params.singularNoun != null) data.singular_noun = params.singularNoun
if (params.pluralNoun != null) 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
View 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.trim()}/records/${params.recordId.trim()}`,
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 {
throw new Error('Invalid JSON provided for record 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' },
},
}
+127
View File
@@ -0,0 +1,127 @@
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.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const data: Record<string, unknown> = {}
if (params.deadlineAt != null) data.deadline_at = params.deadlineAt || null
if (params.isCompleted != null) 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,
completedAt: task.completed_at ?? null,
linkedRecords,
assignees,
createdByActor: task.created_by_actor ?? null,
createdAt: task.created_at ?? null,
},
}
},
outputs: TASK_OUTPUT_PROPERTIES,
}
+100
View 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: 'HTTPS target URL for webhook delivery',
},
subscriptions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON array of subscriptions, e.g. [{"event_type":"note.created"}]',
},
},
request: {
url: (params) => `https://api.attio.com/v2/webhooks/${params.webhookId.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const data: Record<string, unknown> = {}
if (params.targetUrl) data.target_url = params.targetUrl.trim()
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,
}