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
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:
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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'
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
Reference in New Issue
Block a user