mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(attio): add attribute tools + fix API alignment gaps (#5445)
* feat(attio): add attribute tools + fix API alignment gaps
- Add 4 new tools: attio_list_attributes, get_attribute, create_attribute, update_attribute
- Add missing completed_at field to task tools
- Fix note tags output to match Attio's actual response shape (workspace-member vs record tags)
- Fix attribute outputs missing is_default_value_enabled, default_value, relationship
- Fix create_comment sending both entry and thread_id (Attio requires exactly one)
- Wire attribute pagination + task sort into the block
* fix(attio): address review feedback on attribute tools
- Throw on invalid config JSON instead of silently overwriting with {}
(create_attribute, update_attribute)
- create_attribute: omit config field entirely when not provided instead
of always sending {}
- Add "Leave unchanged" option to Required/Unique dropdowns so
update_attribute no longer clears existing constraints on unrelated
field updates
- Fix stale sort param description on list_tasks (was missing
completed_at:asc/desc variants)
* fix(attio): fix silent update-clobber bugs + complete comment mutual-exclusion
- create_comment: support all 3 of Attio's mutually-exclusive comment
targets (thread_id, record, entry), not just thread_id/entry
- Fix update_task silently clearing is_completed on unrelated field
updates (taskIsCompletedUpdate now defaults to "leave unchanged")
- Fix update_list silently resetting workspace_access to full-access on
unrelated field updates (listWorkspaceAccessUpdate, same pattern)
- create_attribute: restore config:{} as always-required per Attio's
schema (previously omitted it entirely, which is invalid on create)
- create_record/update_record/assert_record/list_records: throw on
invalid values/filter/sorts JSON instead of silently substituting {}
(was a silent data-loss risk on malformed input)
- get_task: drop stray Content-Type header on a GET request
- types.ts: remove two dead unreferenced interfaces, fix
workspaceMemberAccess type (was string, is actually an array)
* fix(attio): gate operation-specific param mapping on current operation
Params like taskIsCompleted/listWorkspaceAccess/attributeIsMultiselect/
attributeIsArchived are only meant for one specific operation, but their
mapping wasn't checking params.operation. A stale value persisted in
block state from a prior operation selection (e.g. switching the
dropdown from create_task to update_task) could leak through and
override the "leave unchanged" default on the other operation.
* fix(attio): explicit comment target selector + attribute archived tri-state
- Add "Leave unchanged" default to attributeIsArchived dropdown, same
pattern as isRequired/isUnique, so a stale archived flag from an
earlier edit can't unarchive an attribute on an unrelated update
- create_comment: replace field-presence inference (which let stale
record fields hijack a list-entry comment or vice versa) with an
explicit commentTarget selector (List Entry / Record / Reply to
Thread). Only the fields for the selected target are ever forwarded
* fix(attio): gate remaining unscoped param mappings + comment-target back-compat
- taskFilterCompleted (list_tasks filter) was mapped to isCompleted for
every operation; gate it to list_tasks so it can't override the
isCompleted value on an unrelated update_task call
- Generic threadId (get_thread) was unconditionally forwarded and
create_comment prioritizes thread_id first, so a leftover threadId
from configuring get_thread could silently hijack a comment meant for
a list entry or record; gate it to get_thread
- Default commentTarget to the pre-existing behavior (entry, or thread
if commentThreadId is set) when absent, so blocks saved before this
field existed keep working instead of throwing
* fix(attio): gate every param mapping by its operation, eliminate stale-value class of bugs
The params() function reuses cleanParams keys (title, content, apiSlug,
filter, sorts, recordId, entryId, list, object, threadId, ...) across
several unrelated operation families. Every mapping was unconditional on
presence alone, so a stale value left in block state from a previously
selected operation could silently leak into an unrelated request and
overwrite the intended value (last-write-wins on a shared key).
Rewrote the whole function to gate every line by params.operation
against the exact operation set its subBlock's condition exposes it
under, closing this entire bug class in one pass instead of patching
individual instances as they were found in review.
Also split attributeIsRequired/attributeIsUnique into create-only
(default false) and update-only (tri-state, default "leave unchanged")
variants — the shared field let an explicit Yes/No choice from
create_attribute carry over and silently change constraints on an
unrelated update_attribute call.
* fix(attio): preserve legacy taskIsCompleted/listWorkspaceAccess on update
taskIsCompleted and listWorkspaceAccess pre-date this PR as fields
shared between create and update operations (confirmed present on
origin/staging). Splitting them into create/update variants earlier
this session meant existing saved blocks with a value stored under the
legacy field name would silently stop applying it on update_task /
update_list once the new -Update field (always undefined for old
blocks) took over. Fall back to the legacy field when the new field is
untouched, so old saved workflows keep behaving exactly as before.
This commit is contained in:
+646
-72
@@ -64,6 +64,10 @@ export const AttioBlock: BlockConfig<AttioResponse> = {
|
||||
{ label: 'Create Webhook', id: 'create_webhook' },
|
||||
{ label: 'Update Webhook', id: 'update_webhook' },
|
||||
{ label: 'Delete Webhook', id: 'delete_webhook' },
|
||||
{ label: 'List Attributes', id: 'list_attributes' },
|
||||
{ label: 'Get Attribute', id: 'get_attribute' },
|
||||
{ label: 'Create Attribute', id: 'create_attribute' },
|
||||
{ label: 'Update Attribute', id: 'update_attribute' },
|
||||
],
|
||||
value: () => 'list_records',
|
||||
},
|
||||
@@ -430,7 +434,19 @@ YYYY-MM-DDTHH:mm:ss.SSSZ
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: ['create_task', 'update_task'] },
|
||||
condition: { field: 'operation', value: 'create_task' },
|
||||
},
|
||||
{
|
||||
id: 'taskIsCompletedUpdate',
|
||||
title: 'Completed',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Leave unchanged', id: 'unchanged' },
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'unchanged',
|
||||
condition: { field: 'operation', value: 'update_task' },
|
||||
},
|
||||
{
|
||||
id: 'taskLinkedRecords',
|
||||
@@ -662,7 +678,20 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text.
|
||||
{ label: 'Read Only', id: 'read-only' },
|
||||
],
|
||||
value: () => 'full-access',
|
||||
condition: { field: 'operation', value: ['create_list', 'update_list'] },
|
||||
condition: { field: 'operation', value: 'create_list' },
|
||||
},
|
||||
{
|
||||
id: 'listWorkspaceAccessUpdate',
|
||||
title: 'Workspace Access',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Leave unchanged', id: 'unchanged' },
|
||||
{ label: 'Full Access', id: 'full-access' },
|
||||
{ label: 'Read & Write', id: 'read-and-write' },
|
||||
{ label: 'Read Only', id: 'read-only' },
|
||||
],
|
||||
value: () => 'unchanged',
|
||||
condition: { field: 'operation', value: 'update_list' },
|
||||
},
|
||||
|
||||
// List entry fields
|
||||
@@ -823,29 +852,97 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text.
|
||||
condition: { field: 'operation', value: 'create_comment' },
|
||||
required: { field: 'operation', value: 'create_comment' },
|
||||
},
|
||||
{
|
||||
id: 'commentTarget',
|
||||
title: 'Comment On',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'List Entry', id: 'entry' },
|
||||
{ label: 'Record', id: 'record' },
|
||||
{ label: 'Reply to Thread', id: 'thread' },
|
||||
],
|
||||
value: () => 'entry',
|
||||
condition: { field: 'operation', value: 'create_comment' },
|
||||
},
|
||||
{
|
||||
id: 'commentList',
|
||||
title: 'List',
|
||||
type: 'short-input',
|
||||
placeholder: 'List ID or slug',
|
||||
condition: { field: 'operation', value: 'create_comment' },
|
||||
required: { field: 'operation', value: 'create_comment' },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'create_comment',
|
||||
and: { field: 'commentTarget', value: 'entry' },
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: 'create_comment',
|
||||
and: { field: 'commentTarget', value: 'entry' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'commentEntryId',
|
||||
title: 'Entry ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'List entry ID to comment on',
|
||||
condition: { field: 'operation', value: 'create_comment' },
|
||||
required: { field: 'operation', value: 'create_comment' },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'create_comment',
|
||||
and: { field: 'commentTarget', value: 'entry' },
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: 'create_comment',
|
||||
and: { field: 'commentTarget', value: 'entry' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'commentRecordObject',
|
||||
title: 'Record Object',
|
||||
type: 'short-input',
|
||||
placeholder: 'Object ID or slug the record belongs to',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'create_comment',
|
||||
and: { field: 'commentTarget', value: 'record' },
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: 'create_comment',
|
||||
and: { field: 'commentTarget', value: 'record' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'commentRecordId',
|
||||
title: 'Record ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Record ID to comment on directly',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'create_comment',
|
||||
and: { field: 'commentTarget', value: 'record' },
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: 'create_comment',
|
||||
and: { field: 'commentTarget', value: 'record' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'commentThreadId',
|
||||
title: 'Thread ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Reply to thread (optional, omit to start new)',
|
||||
condition: { field: 'operation', value: 'create_comment' },
|
||||
mode: 'advanced',
|
||||
placeholder: 'Thread ID to reply to',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'create_comment',
|
||||
and: { field: 'commentTarget', value: 'thread' },
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: 'create_comment',
|
||||
and: { field: 'commentTarget', value: 'thread' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'commentCreatedAt',
|
||||
@@ -983,6 +1080,208 @@ workspace-member.created
|
||||
},
|
||||
},
|
||||
|
||||
// Attribute fields
|
||||
{
|
||||
id: 'attributeTarget',
|
||||
title: 'Target',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Object', id: 'objects' },
|
||||
{ label: 'List', id: 'lists' },
|
||||
],
|
||||
value: () => 'objects',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['list_attributes', 'get_attribute', 'create_attribute', 'update_attribute'],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['list_attributes', 'get_attribute', 'create_attribute', 'update_attribute'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'attributeIdentifier',
|
||||
title: 'Object or List ID/Slug',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g. people, companies',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['list_attributes', 'get_attribute', 'create_attribute', 'update_attribute'],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['list_attributes', 'get_attribute', 'create_attribute', 'update_attribute'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'attributeId',
|
||||
title: 'Attribute ID or Slug',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g. email_addresses',
|
||||
condition: { field: 'operation', value: ['get_attribute', 'update_attribute'] },
|
||||
required: { field: 'operation', value: ['get_attribute', 'update_attribute'] },
|
||||
},
|
||||
{
|
||||
id: 'attributeTitle',
|
||||
title: 'Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g. Lead Source',
|
||||
condition: { field: 'operation', value: ['create_attribute', 'update_attribute'] },
|
||||
required: { field: 'operation', value: 'create_attribute' },
|
||||
},
|
||||
{
|
||||
id: 'attributeApiSlug',
|
||||
title: 'API Slug',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g. lead_source',
|
||||
condition: { field: 'operation', value: ['create_attribute', 'update_attribute'] },
|
||||
required: { field: 'operation', value: 'create_attribute' },
|
||||
},
|
||||
{
|
||||
id: 'attributeType',
|
||||
title: 'Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Text', id: 'text' },
|
||||
{ label: 'Number', id: 'number' },
|
||||
{ label: 'Checkbox', id: 'checkbox' },
|
||||
{ label: 'Currency', id: 'currency' },
|
||||
{ label: 'Date', id: 'date' },
|
||||
{ label: 'Timestamp', id: 'timestamp' },
|
||||
{ label: 'Rating', id: 'rating' },
|
||||
{ label: 'Status', id: 'status' },
|
||||
{ label: 'Select', id: 'select' },
|
||||
{ label: 'Record Reference', id: 'record-reference' },
|
||||
{ label: 'Actor Reference', id: 'actor-reference' },
|
||||
{ label: 'Location', id: 'location' },
|
||||
{ label: 'Domain', id: 'domain' },
|
||||
{ label: 'Email Address', id: 'email-address' },
|
||||
{ label: 'Phone Number', id: 'phone-number' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_attribute' },
|
||||
required: { field: 'operation', value: 'create_attribute' },
|
||||
},
|
||||
{
|
||||
id: 'attributeDescription',
|
||||
title: 'Description',
|
||||
type: 'long-input',
|
||||
placeholder: 'Describe the attribute',
|
||||
condition: { field: 'operation', value: ['create_attribute', 'update_attribute'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'attributeIsRequired',
|
||||
title: 'Required',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'create_attribute' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'attributeIsRequiredUpdate',
|
||||
title: 'Required',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Leave unchanged', id: 'unchanged' },
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'unchanged',
|
||||
condition: { field: 'operation', value: 'update_attribute' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'attributeIsUnique',
|
||||
title: 'Unique',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'create_attribute' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'attributeIsUniqueUpdate',
|
||||
title: 'Unique',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Leave unchanged', id: 'unchanged' },
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'unchanged',
|
||||
condition: { field: 'operation', value: 'update_attribute' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'attributeIsMultiselect',
|
||||
title: 'Multiselect',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'create_attribute' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'attributeIsArchived',
|
||||
title: 'Archived',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Leave unchanged', id: 'unchanged' },
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'unchanged',
|
||||
condition: { field: 'operation', value: 'update_attribute' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'attributeConfig',
|
||||
title: 'Config',
|
||||
type: 'code',
|
||||
placeholder: '{"default_currency_code": "USD", "display_type": "text"}',
|
||||
condition: { field: 'operation', value: ['create_attribute', 'update_attribute'] },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `Generate Attio attribute type configuration as a JSON object.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the JSON object. No explanations, no markdown, no extra text.
|
||||
|
||||
### EXAMPLES
|
||||
Currency: {"default_currency_code": "USD", "display_type": "text"}
|
||||
Record reference: {"allowed_objects": ["people", "companies"]}`,
|
||||
placeholder: 'Describe the attribute configuration...',
|
||||
generationType: 'json-object',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'attributeShowArchived',
|
||||
title: 'Show Archived',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'list_attributes' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// Shared limit
|
||||
{
|
||||
id: 'limit',
|
||||
@@ -1000,6 +1299,7 @@ workspace-member.created
|
||||
'query_list_entries',
|
||||
'list_threads',
|
||||
'list_webhooks',
|
||||
'list_attributes',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -1018,9 +1318,23 @@ workspace-member.created
|
||||
'query_list_entries',
|
||||
'list_threads',
|
||||
'list_webhooks',
|
||||
'list_attributes',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'taskSort',
|
||||
title: 'Sort',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Created At (asc)', id: 'created_at:asc' },
|
||||
{ label: 'Created At (desc)', id: 'created_at:desc' },
|
||||
{ label: 'Completed At (asc)', id: 'completed_at:asc' },
|
||||
{ label: 'Completed At (desc)', id: 'completed_at:desc' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'list_tasks' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
...getTrigger('attio_record_created').subBlocks,
|
||||
...getTrigger('attio_record_updated').subBlocks,
|
||||
...getTrigger('attio_record_deleted').subBlocks,
|
||||
@@ -1116,6 +1430,10 @@ workspace-member.created
|
||||
'attio_create_webhook',
|
||||
'attio_update_webhook',
|
||||
'attio_delete_webhook',
|
||||
'attio_list_attributes',
|
||||
'attio_get_attribute',
|
||||
'attio_create_attribute',
|
||||
'attio_update_attribute',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => `attio_${params.operation}`,
|
||||
@@ -1123,92 +1441,285 @@ workspace-member.created
|
||||
const cleanParams: Record<string, unknown> = {
|
||||
oauthCredential: params.oauthCredential,
|
||||
}
|
||||
const op = params.operation
|
||||
|
||||
// Record params
|
||||
if (params.objectType) cleanParams.objectType = params.objectType
|
||||
if (params.recordId) cleanParams.recordId = params.recordId
|
||||
if (params.matchingAttribute) cleanParams.matchingAttribute = params.matchingAttribute
|
||||
if (params.values) cleanParams.values = params.values
|
||||
if (params.filter) cleanParams.filter = params.filter
|
||||
if (params.sorts) cleanParams.sorts = params.sorts
|
||||
if (params.query) cleanParams.query = params.query
|
||||
if (params.objects) cleanParams.objects = params.objects
|
||||
// Record params — each field is only sent for the operations whose subBlock condition
|
||||
// actually exposes it, so a stale value left over from switching operations never leaks
|
||||
// into an unrelated request (cleanParams keys are reused across several operation families).
|
||||
if (
|
||||
[
|
||||
'list_records',
|
||||
'get_record',
|
||||
'create_record',
|
||||
'update_record',
|
||||
'delete_record',
|
||||
'assert_record',
|
||||
].includes(op) &&
|
||||
params.objectType
|
||||
)
|
||||
cleanParams.objectType = params.objectType
|
||||
if (['get_record', 'update_record', 'delete_record'].includes(op) && params.recordId)
|
||||
cleanParams.recordId = params.recordId
|
||||
if (op === 'assert_record' && params.matchingAttribute)
|
||||
cleanParams.matchingAttribute = params.matchingAttribute
|
||||
if (['create_record', 'update_record', 'assert_record'].includes(op) && params.values)
|
||||
cleanParams.values = params.values
|
||||
if (op === 'list_records' && params.filter) cleanParams.filter = params.filter
|
||||
if (op === 'list_records' && params.sorts) cleanParams.sorts = params.sorts
|
||||
if (op === 'search_records' && params.query) cleanParams.query = params.query
|
||||
if (op === 'search_records' && params.objects) cleanParams.objects = params.objects
|
||||
|
||||
// Note params
|
||||
if (params.noteParentObject) cleanParams.parentObject = params.noteParentObject
|
||||
if (params.noteParentRecordId) cleanParams.parentRecordId = params.noteParentRecordId
|
||||
if (params.noteTitle) cleanParams.title = params.noteTitle
|
||||
if (params.noteContent) cleanParams.content = params.noteContent
|
||||
if (params.noteFormat) cleanParams.format = params.noteFormat
|
||||
if (params.noteId) cleanParams.noteId = params.noteId
|
||||
if (params.noteCreatedAt) cleanParams.createdAt = params.noteCreatedAt
|
||||
if (params.noteMeetingId) cleanParams.meetingId = params.noteMeetingId
|
||||
if (['list_notes', 'create_note'].includes(op) && params.noteParentObject)
|
||||
cleanParams.parentObject = params.noteParentObject
|
||||
if (['list_notes', 'create_note'].includes(op) && params.noteParentRecordId)
|
||||
cleanParams.parentRecordId = params.noteParentRecordId
|
||||
if (op === 'create_note' && params.noteTitle) cleanParams.title = params.noteTitle
|
||||
if (op === 'create_note' && params.noteContent) cleanParams.content = params.noteContent
|
||||
if (op === 'create_note' && params.noteFormat) cleanParams.format = params.noteFormat
|
||||
if (op === 'create_note' && params.noteCreatedAt)
|
||||
cleanParams.createdAt = params.noteCreatedAt
|
||||
if (op === 'create_note' && params.noteMeetingId)
|
||||
cleanParams.meetingId = params.noteMeetingId
|
||||
if (['get_note', 'delete_note'].includes(op) && params.noteId)
|
||||
cleanParams.noteId = params.noteId
|
||||
|
||||
// Task params
|
||||
if (params.taskContent) cleanParams.content = params.taskContent
|
||||
if (params.taskDeadline) cleanParams.deadlineAt = params.taskDeadline
|
||||
if (params.taskIsCompleted !== undefined)
|
||||
if (op === 'create_task' && params.taskContent) cleanParams.content = params.taskContent
|
||||
if (['create_task', 'update_task'].includes(op) && params.taskDeadline)
|
||||
cleanParams.deadlineAt = params.taskDeadline
|
||||
if (op === 'create_task' && params.taskIsCompleted !== undefined)
|
||||
cleanParams.isCompleted =
|
||||
params.taskIsCompleted === 'true' || params.taskIsCompleted === true
|
||||
if (params.taskLinkedRecords) cleanParams.linkedRecords = params.taskLinkedRecords
|
||||
if (params.taskAssignees) cleanParams.assignees = params.taskAssignees
|
||||
if (params.taskId) cleanParams.taskId = params.taskId
|
||||
if (params.taskFilterObject) cleanParams.linkedObject = params.taskFilterObject
|
||||
if (params.taskFilterRecordId) cleanParams.linkedRecordId = params.taskFilterRecordId
|
||||
if (params.taskFilterAssignee) cleanParams.assignee = params.taskFilterAssignee
|
||||
if (params.taskFilterCompleted && params.taskFilterCompleted !== 'all')
|
||||
if (op === 'update_task') {
|
||||
// taskIsCompletedUpdate is the current control; taskIsCompleted is a fallback for blocks
|
||||
// saved before the split, when that field applied to both create_task and update_task.
|
||||
if (
|
||||
params.taskIsCompletedUpdate !== undefined &&
|
||||
params.taskIsCompletedUpdate !== 'unchanged'
|
||||
) {
|
||||
cleanParams.isCompleted =
|
||||
params.taskIsCompletedUpdate === 'true' || params.taskIsCompletedUpdate === true
|
||||
} else if (
|
||||
params.taskIsCompletedUpdate === undefined &&
|
||||
params.taskIsCompleted !== undefined
|
||||
) {
|
||||
cleanParams.isCompleted =
|
||||
params.taskIsCompleted === 'true' || params.taskIsCompleted === true
|
||||
}
|
||||
}
|
||||
if (['create_task', 'update_task'].includes(op) && params.taskLinkedRecords)
|
||||
cleanParams.linkedRecords = params.taskLinkedRecords
|
||||
if (['create_task', 'update_task'].includes(op) && params.taskAssignees)
|
||||
cleanParams.assignees = params.taskAssignees
|
||||
if (['get_task', 'update_task', 'delete_task'].includes(op) && params.taskId)
|
||||
cleanParams.taskId = params.taskId
|
||||
if (op === 'list_tasks' && params.taskFilterObject)
|
||||
cleanParams.linkedObject = params.taskFilterObject
|
||||
if (op === 'list_tasks' && params.taskFilterRecordId)
|
||||
cleanParams.linkedRecordId = params.taskFilterRecordId
|
||||
if (op === 'list_tasks' && params.taskFilterAssignee)
|
||||
cleanParams.assignee = params.taskFilterAssignee
|
||||
if (
|
||||
op === 'list_tasks' &&
|
||||
params.taskFilterCompleted &&
|
||||
params.taskFilterCompleted !== 'all'
|
||||
)
|
||||
cleanParams.isCompleted = params.taskFilterCompleted === 'true'
|
||||
if (op === 'list_tasks' && params.taskSort) cleanParams.sort = params.taskSort
|
||||
|
||||
// Object params
|
||||
if (params.objectIdOrSlug) cleanParams.object = params.objectIdOrSlug
|
||||
if (params.objectApiSlug) cleanParams.apiSlug = params.objectApiSlug
|
||||
if (params.objectSingularNoun) cleanParams.singularNoun = params.objectSingularNoun
|
||||
if (params.objectPluralNoun) cleanParams.pluralNoun = params.objectPluralNoun
|
||||
if (['get_object', 'update_object'].includes(op) && params.objectIdOrSlug)
|
||||
cleanParams.object = params.objectIdOrSlug
|
||||
if (['create_object', 'update_object'].includes(op) && params.objectApiSlug)
|
||||
cleanParams.apiSlug = params.objectApiSlug
|
||||
if (['create_object', 'update_object'].includes(op) && params.objectSingularNoun)
|
||||
cleanParams.singularNoun = params.objectSingularNoun
|
||||
if (['create_object', 'update_object'].includes(op) && params.objectPluralNoun)
|
||||
cleanParams.pluralNoun = params.objectPluralNoun
|
||||
|
||||
// List params
|
||||
if (params.listIdOrSlug) cleanParams.list = params.listIdOrSlug
|
||||
if (params.listName) cleanParams.name = params.listName
|
||||
if (params.listParentObject) cleanParams.parentObject = params.listParentObject
|
||||
if (params.listApiSlug) cleanParams.apiSlug = params.listApiSlug
|
||||
if (params.listWorkspaceAccess) cleanParams.workspaceAccess = params.listWorkspaceAccess
|
||||
if (
|
||||
[
|
||||
'get_list',
|
||||
'update_list',
|
||||
'query_list_entries',
|
||||
'get_list_entry',
|
||||
'create_list_entry',
|
||||
'update_list_entry',
|
||||
'delete_list_entry',
|
||||
].includes(op) &&
|
||||
params.listIdOrSlug
|
||||
)
|
||||
cleanParams.list = params.listIdOrSlug
|
||||
if (['create_list', 'update_list'].includes(op) && params.listName)
|
||||
cleanParams.name = params.listName
|
||||
if (op === 'create_list' && params.listParentObject)
|
||||
cleanParams.parentObject = params.listParentObject
|
||||
if (['create_list', 'update_list'].includes(op) && params.listApiSlug)
|
||||
cleanParams.apiSlug = params.listApiSlug
|
||||
if (op === 'create_list' && params.listWorkspaceAccess)
|
||||
cleanParams.workspaceAccess = params.listWorkspaceAccess
|
||||
if (op === 'update_list') {
|
||||
// listWorkspaceAccessUpdate is the current control; listWorkspaceAccess is a fallback
|
||||
// for blocks saved before the split, when that field applied to both create and update.
|
||||
if (
|
||||
params.listWorkspaceAccessUpdate &&
|
||||
params.listWorkspaceAccessUpdate !== 'unchanged'
|
||||
) {
|
||||
cleanParams.workspaceAccess = params.listWorkspaceAccessUpdate
|
||||
} else if (!params.listWorkspaceAccessUpdate && params.listWorkspaceAccess) {
|
||||
cleanParams.workspaceAccess = params.listWorkspaceAccess
|
||||
}
|
||||
}
|
||||
|
||||
// List entry params
|
||||
if (params.entryId) cleanParams.entryId = params.entryId
|
||||
if (params.entryParentRecordId) cleanParams.parentRecordId = params.entryParentRecordId
|
||||
if (params.entryParentObject) cleanParams.parentObject = params.entryParentObject
|
||||
if (params.entryValues) cleanParams.entryValues = params.entryValues
|
||||
if (params.entryFilter) cleanParams.filter = params.entryFilter
|
||||
if (params.entrySorts) cleanParams.sorts = params.entrySorts
|
||||
if (
|
||||
['get_list_entry', 'update_list_entry', 'delete_list_entry'].includes(op) &&
|
||||
params.entryId
|
||||
)
|
||||
cleanParams.entryId = params.entryId
|
||||
if (op === 'create_list_entry' && params.entryParentRecordId)
|
||||
cleanParams.parentRecordId = params.entryParentRecordId
|
||||
if (op === 'create_list_entry' && params.entryParentObject)
|
||||
cleanParams.parentObject = params.entryParentObject
|
||||
if (['create_list_entry', 'update_list_entry'].includes(op) && params.entryValues)
|
||||
cleanParams.entryValues = params.entryValues
|
||||
if (op === 'query_list_entries' && params.entryFilter)
|
||||
cleanParams.filter = params.entryFilter
|
||||
if (op === 'query_list_entries' && params.entrySorts) cleanParams.sorts = params.entrySorts
|
||||
|
||||
// Member params
|
||||
if (params.memberId) cleanParams.memberId = params.memberId
|
||||
if (op === 'get_member' && params.memberId) cleanParams.memberId = params.memberId
|
||||
|
||||
// Comment params
|
||||
if (params.commentContent) cleanParams.content = params.commentContent
|
||||
if (params.commentFormat) cleanParams.format = params.commentFormat
|
||||
if (params.commentAuthorType) cleanParams.authorType = params.commentAuthorType
|
||||
if (params.commentAuthorId) cleanParams.authorId = params.commentAuthorId
|
||||
if (params.commentList) cleanParams.list = params.commentList
|
||||
if (params.commentEntryId) cleanParams.entryId = params.commentEntryId
|
||||
if (params.commentThreadId) cleanParams.threadId = params.commentThreadId
|
||||
if (params.commentCreatedAt) cleanParams.createdAt = params.commentCreatedAt
|
||||
if (params.commentId) cleanParams.commentId = params.commentId
|
||||
if (op === 'create_comment') {
|
||||
if (params.commentContent) cleanParams.content = params.commentContent
|
||||
if (params.commentFormat) cleanParams.format = params.commentFormat
|
||||
if (params.commentAuthorType) cleanParams.authorType = params.commentAuthorType
|
||||
if (params.commentAuthorId) cleanParams.authorId = params.commentAuthorId
|
||||
// Blocks saved before commentTarget existed have no value for it — fall back to the
|
||||
// pre-existing behavior (entry-based comment, or thread reply if a thread ID was set).
|
||||
const commentTarget =
|
||||
params.commentTarget ?? (params.commentThreadId ? 'thread' : 'entry')
|
||||
if (commentTarget === 'entry') {
|
||||
if (params.commentList) cleanParams.list = params.commentList
|
||||
if (params.commentEntryId) cleanParams.entryId = params.commentEntryId
|
||||
} else if (commentTarget === 'record') {
|
||||
if (params.commentRecordObject) cleanParams.recordObject = params.commentRecordObject
|
||||
if (params.commentRecordId) cleanParams.recordId = params.commentRecordId
|
||||
} else if (commentTarget === 'thread') {
|
||||
if (params.commentThreadId) cleanParams.threadId = params.commentThreadId
|
||||
}
|
||||
if (params.commentCreatedAt) cleanParams.createdAt = params.commentCreatedAt
|
||||
}
|
||||
if (['get_comment', 'delete_comment'].includes(op) && params.commentId)
|
||||
cleanParams.commentId = params.commentId
|
||||
|
||||
// Thread params
|
||||
if (params.threadId) cleanParams.threadId = params.threadId
|
||||
if (params.threadFilterRecordId) cleanParams.recordId = params.threadFilterRecordId
|
||||
if (params.threadFilterObject) cleanParams.object = params.threadFilterObject
|
||||
if (params.threadFilterEntryId) cleanParams.entryId = params.threadFilterEntryId
|
||||
if (params.threadFilterList) cleanParams.list = params.threadFilterList
|
||||
if (op === 'get_thread' && params.threadId) cleanParams.threadId = params.threadId
|
||||
if (op === 'list_threads' && params.threadFilterRecordId)
|
||||
cleanParams.recordId = params.threadFilterRecordId
|
||||
if (op === 'list_threads' && params.threadFilterObject)
|
||||
cleanParams.object = params.threadFilterObject
|
||||
if (op === 'list_threads' && params.threadFilterEntryId)
|
||||
cleanParams.entryId = params.threadFilterEntryId
|
||||
if (op === 'list_threads' && params.threadFilterList)
|
||||
cleanParams.list = params.threadFilterList
|
||||
|
||||
// Webhook params
|
||||
if (params.webhookId) cleanParams.webhookId = params.webhookId
|
||||
if (params.webhookTargetUrl) cleanParams.targetUrl = params.webhookTargetUrl
|
||||
if (params.webhookSubscriptions) cleanParams.subscriptions = params.webhookSubscriptions
|
||||
if (['get_webhook', 'update_webhook', 'delete_webhook'].includes(op) && params.webhookId)
|
||||
cleanParams.webhookId = params.webhookId
|
||||
if (['create_webhook', 'update_webhook'].includes(op) && params.webhookTargetUrl)
|
||||
cleanParams.targetUrl = params.webhookTargetUrl
|
||||
if (['create_webhook', 'update_webhook'].includes(op) && params.webhookSubscriptions)
|
||||
cleanParams.subscriptions = params.webhookSubscriptions
|
||||
|
||||
// Shared params
|
||||
if (params.limit) cleanParams.limit = Number(params.limit)
|
||||
if (params.offset) cleanParams.offset = Number(params.offset)
|
||||
// Attribute params
|
||||
const attributeOps = [
|
||||
'list_attributes',
|
||||
'get_attribute',
|
||||
'create_attribute',
|
||||
'update_attribute',
|
||||
]
|
||||
if (attributeOps.includes(op) && params.attributeTarget)
|
||||
cleanParams.target = params.attributeTarget
|
||||
if (attributeOps.includes(op) && params.attributeIdentifier)
|
||||
cleanParams.identifier = params.attributeIdentifier
|
||||
if (['get_attribute', 'update_attribute'].includes(op) && params.attributeId)
|
||||
cleanParams.attribute = params.attributeId
|
||||
if (['create_attribute', 'update_attribute'].includes(op) && params.attributeTitle)
|
||||
cleanParams.title = params.attributeTitle
|
||||
if (['create_attribute', 'update_attribute'].includes(op) && params.attributeApiSlug)
|
||||
cleanParams.apiSlug = params.attributeApiSlug
|
||||
if (op === 'create_attribute' && params.attributeType)
|
||||
cleanParams.type = params.attributeType
|
||||
if (['create_attribute', 'update_attribute'].includes(op) && params.attributeDescription)
|
||||
cleanParams.description = params.attributeDescription
|
||||
if (op === 'create_attribute' && params.attributeIsRequired !== undefined)
|
||||
cleanParams.isRequired =
|
||||
params.attributeIsRequired === 'true' || params.attributeIsRequired === true
|
||||
if (
|
||||
op === 'update_attribute' &&
|
||||
params.attributeIsRequiredUpdate !== undefined &&
|
||||
params.attributeIsRequiredUpdate !== 'unchanged'
|
||||
)
|
||||
cleanParams.isRequired =
|
||||
params.attributeIsRequiredUpdate === 'true' || params.attributeIsRequiredUpdate === true
|
||||
if (op === 'create_attribute' && params.attributeIsUnique !== undefined)
|
||||
cleanParams.isUnique =
|
||||
params.attributeIsUnique === 'true' || params.attributeIsUnique === true
|
||||
if (
|
||||
op === 'update_attribute' &&
|
||||
params.attributeIsUniqueUpdate !== undefined &&
|
||||
params.attributeIsUniqueUpdate !== 'unchanged'
|
||||
)
|
||||
cleanParams.isUnique =
|
||||
params.attributeIsUniqueUpdate === 'true' || params.attributeIsUniqueUpdate === true
|
||||
if (op === 'create_attribute' && params.attributeIsMultiselect !== undefined)
|
||||
cleanParams.isMultiselect =
|
||||
params.attributeIsMultiselect === 'true' || params.attributeIsMultiselect === true
|
||||
if (
|
||||
op === 'update_attribute' &&
|
||||
params.attributeIsArchived !== undefined &&
|
||||
params.attributeIsArchived !== 'unchanged'
|
||||
)
|
||||
cleanParams.isArchived =
|
||||
params.attributeIsArchived === 'true' || params.attributeIsArchived === true
|
||||
if (['create_attribute', 'update_attribute'].includes(op) && params.attributeConfig)
|
||||
cleanParams.config = params.attributeConfig
|
||||
if (op === 'list_attributes' && params.attributeShowArchived !== undefined)
|
||||
cleanParams.showArchived =
|
||||
params.attributeShowArchived === 'true' || params.attributeShowArchived === true
|
||||
|
||||
// Shared pagination params — only meaningful for list-style operations
|
||||
if (
|
||||
[
|
||||
'list_records',
|
||||
'search_records',
|
||||
'list_notes',
|
||||
'list_tasks',
|
||||
'query_list_entries',
|
||||
'list_threads',
|
||||
'list_webhooks',
|
||||
'list_attributes',
|
||||
].includes(op) &&
|
||||
params.limit
|
||||
)
|
||||
cleanParams.limit = Number(params.limit)
|
||||
if (
|
||||
[
|
||||
'list_records',
|
||||
'list_notes',
|
||||
'list_tasks',
|
||||
'query_list_entries',
|
||||
'list_threads',
|
||||
'list_webhooks',
|
||||
'list_attributes',
|
||||
].includes(op) &&
|
||||
params.offset
|
||||
)
|
||||
cleanParams.offset = Number(params.offset)
|
||||
|
||||
return cleanParams
|
||||
},
|
||||
@@ -1237,6 +1748,7 @@ workspace-member.created
|
||||
taskContent: { type: 'string', description: 'Task content' },
|
||||
taskDeadline: { type: 'string', description: 'Task deadline' },
|
||||
taskIsCompleted: { type: 'string', description: 'Task completion status' },
|
||||
taskIsCompletedUpdate: { type: 'string', description: 'Task completion status (update)' },
|
||||
taskLinkedRecords: { type: 'json', description: 'Linked records JSON array' },
|
||||
taskAssignees: { type: 'json', description: 'Assignees JSON array' },
|
||||
taskId: { type: 'string', description: 'Task ID' },
|
||||
@@ -1249,6 +1761,10 @@ workspace-member.created
|
||||
listParentObject: { type: 'string', description: 'List parent object' },
|
||||
listApiSlug: { type: 'string', description: 'List API slug' },
|
||||
listWorkspaceAccess: { type: 'string', description: 'List workspace-level access' },
|
||||
listWorkspaceAccessUpdate: {
|
||||
type: 'string',
|
||||
description: 'List workspace-level access (update)',
|
||||
},
|
||||
entryId: { type: 'string', description: 'List entry ID' },
|
||||
entryParentRecordId: { type: 'string', description: 'Record ID for list entry' },
|
||||
entryParentObject: { type: 'string', description: 'Record object type for list entry' },
|
||||
@@ -1260,8 +1776,11 @@ workspace-member.created
|
||||
commentFormat: { type: 'string', description: 'Comment format' },
|
||||
commentAuthorType: { type: 'string', description: 'Comment author type' },
|
||||
commentAuthorId: { type: 'string', description: 'Comment author ID' },
|
||||
commentTarget: { type: 'string', description: 'What the comment is attached to' },
|
||||
commentList: { type: 'string', description: 'List for comment' },
|
||||
commentEntryId: { type: 'string', description: 'Entry ID for comment' },
|
||||
commentRecordObject: { type: 'string', description: 'Object for record comment' },
|
||||
commentRecordId: { type: 'string', description: 'Record ID for record comment' },
|
||||
commentThreadId: { type: 'string', description: 'Thread ID to reply to' },
|
||||
commentCreatedAt: { type: 'string', description: 'Comment creation timestamp (backdate)' },
|
||||
commentId: { type: 'string', description: 'Comment ID' },
|
||||
@@ -1269,6 +1788,34 @@ workspace-member.created
|
||||
webhookId: { type: 'string', description: 'Webhook ID' },
|
||||
webhookTargetUrl: { type: 'string', description: 'Webhook target URL' },
|
||||
webhookSubscriptions: { type: 'json', description: 'Webhook event subscriptions' },
|
||||
attributeTarget: {
|
||||
type: 'string',
|
||||
description: 'Whether the attribute is on an object or list',
|
||||
},
|
||||
attributeIdentifier: { type: 'string', description: 'The object or list ID or slug' },
|
||||
attributeId: { type: 'string', description: 'The attribute ID or slug' },
|
||||
attributeTitle: { type: 'string', description: 'The attribute display title' },
|
||||
attributeApiSlug: { type: 'string', description: 'The attribute API slug' },
|
||||
attributeType: { type: 'string', description: 'The attribute value type' },
|
||||
attributeDescription: { type: 'string', description: 'The attribute description' },
|
||||
attributeIsRequired: { type: 'string', description: 'Whether the attribute is required' },
|
||||
attributeIsRequiredUpdate: {
|
||||
type: 'string',
|
||||
description: 'Whether the attribute is required (update)',
|
||||
},
|
||||
attributeIsUnique: { type: 'string', description: 'Whether the attribute is unique' },
|
||||
attributeIsUniqueUpdate: {
|
||||
type: 'string',
|
||||
description: 'Whether the attribute is unique (update)',
|
||||
},
|
||||
attributeIsMultiselect: { type: 'string', description: 'Whether the attribute is multiselect' },
|
||||
attributeIsArchived: { type: 'string', description: 'Whether the attribute is archived' },
|
||||
attributeConfig: { type: 'json', description: 'Type-dependent attribute configuration' },
|
||||
attributeShowArchived: {
|
||||
type: 'string',
|
||||
description: 'Whether to include archived attributes',
|
||||
},
|
||||
taskSort: { type: 'string', description: 'Task list sort order' },
|
||||
limit: { type: 'string', description: 'Maximum number of results' },
|
||||
offset: { type: 'string', description: 'Number of results to skip for pagination' },
|
||||
},
|
||||
@@ -1289,6 +1836,7 @@ workspace-member.created
|
||||
content: { type: 'string', description: 'Task or note content' },
|
||||
deadlineAt: { type: 'string', description: 'Task deadline' },
|
||||
isCompleted: { type: 'boolean', description: 'Task completion status' },
|
||||
completedAt: { type: 'string', description: 'When the task was completed' },
|
||||
linkedRecords: { type: 'json', description: 'Linked records' },
|
||||
assignees: { type: 'json', description: 'Task assignees' },
|
||||
objects: { type: 'json', description: 'Array of objects' },
|
||||
@@ -1322,6 +1870,32 @@ workspace-member.created
|
||||
deleted: { type: 'boolean', description: 'Whether the item was deleted' },
|
||||
createdAt: { type: 'string', description: 'When the item was created' },
|
||||
success: { type: 'boolean', description: 'Whether the operation succeeded' },
|
||||
attributes: { type: 'json', description: 'Array of attributes' },
|
||||
attributeId: { type: 'string', description: 'The attribute ID' },
|
||||
description: { type: 'string', description: 'The attribute description' },
|
||||
type: { type: 'string', description: 'The attribute value type' },
|
||||
isSystemAttribute: {
|
||||
type: 'boolean',
|
||||
description: 'Whether this is a built-in system attribute',
|
||||
},
|
||||
isWritable: { type: 'boolean', description: 'Whether the attribute can be written to' },
|
||||
isRequired: { type: 'boolean', description: 'Whether the attribute is required' },
|
||||
isUnique: { type: 'boolean', description: 'Whether the attribute enforces uniqueness' },
|
||||
isMultiselect: { type: 'boolean', description: 'Whether the attribute is multiselect' },
|
||||
isDefaultValueEnabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether this attribute has a default value enabled',
|
||||
},
|
||||
isArchived: { type: 'boolean', description: 'Whether the attribute is archived' },
|
||||
defaultValue: {
|
||||
type: 'json',
|
||||
description: 'The default value for this attribute, if enabled',
|
||||
},
|
||||
relationship: {
|
||||
type: 'json',
|
||||
description: 'The related attribute, if this attribute is part of a relationship',
|
||||
},
|
||||
config: { type: 'json', description: 'Type-dependent attribute configuration' },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -56,11 +56,11 @@ export const attioAssertRecordTool: ToolConfig<AttioAssertRecordParams, AttioAss
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let values: Record<string, unknown> = {}
|
||||
let values: Record<string, unknown>
|
||||
try {
|
||||
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
|
||||
} catch {
|
||||
values = {}
|
||||
throw new Error('Invalid JSON provided for record values')
|
||||
}
|
||||
return { data: { values } }
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -52,21 +52,37 @@ export const attioCreateCommentTool: ToolConfig<
|
||||
},
|
||||
list: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The list ID or slug the entry belongs to',
|
||||
description:
|
||||
'The list ID or slug the entry belongs to (used with entryId; omit if threadId or recordId is set)',
|
||||
},
|
||||
entryId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The entry ID to comment on',
|
||||
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)',
|
||||
description: 'Thread ID to reply to (omit to start a new thread on a record or list entry)',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
@@ -91,12 +107,25 @@ export const attioCreateCommentTool: ToolConfig<
|
||||
type: params.authorType,
|
||||
id: params.authorId,
|
||||
},
|
||||
entry: {
|
||||
}
|
||||
// Attio's comment body accepts exactly one of `thread_id`, `record`, or `entry` — mutually exclusive.
|
||||
if (params.threadId) {
|
||||
data.thread_id = params.threadId
|
||||
} else if (params.recordObject && params.recordId) {
|
||||
data.record = {
|
||||
object: params.recordObject,
|
||||
record_id: params.recordId,
|
||||
}
|
||||
} else if (params.list && params.entryId) {
|
||||
data.entry = {
|
||||
list: params.list,
|
||||
entry_id: params.entryId,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'Must provide either threadId, both recordObject and recordId, or both list and entryId'
|
||||
)
|
||||
}
|
||||
if (params.threadId) data.thread_id = params.threadId
|
||||
if (params.createdAt) data.created_at = params.createdAt
|
||||
return { data }
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioCreateNoteParams, AttioCreateNoteResponse } from './types'
|
||||
import { NOTE_OUTPUT_PROPERTIES } from './types'
|
||||
import { mapNoteTags, NOTE_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioCreateNote')
|
||||
|
||||
@@ -105,7 +105,7 @@ export const attioCreateNoteTool: ToolConfig<AttioCreateNoteParams, AttioCreateN
|
||||
contentPlaintext: note.content_plaintext ?? null,
|
||||
contentMarkdown: note.content_markdown ?? null,
|
||||
meetingId: note.meeting_id ?? null,
|
||||
tags: note.tags ?? [],
|
||||
tags: mapNoteTags(note.tags),
|
||||
createdByActor: note.created_by_actor ?? null,
|
||||
createdAt: note.created_at ?? null,
|
||||
},
|
||||
|
||||
@@ -50,7 +50,7 @@ export const attioCreateRecordTool: ToolConfig<AttioCreateRecordParams, AttioCre
|
||||
try {
|
||||
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
|
||||
} catch {
|
||||
values = {}
|
||||
throw new Error('Invalid JSON provided for record values')
|
||||
}
|
||||
return { data: { values } }
|
||||
},
|
||||
|
||||
@@ -124,6 +124,7 @@ export const attioCreateTaskTool: ToolConfig<AttioCreateTaskParams, AttioCreateT
|
||||
content: task.content_plaintext ?? null,
|
||||
deadlineAt: task.deadline_at ?? null,
|
||||
isCompleted: task.is_completed ?? false,
|
||||
completedAt: task.completed_at ?? null,
|
||||
linkedRecords,
|
||||
assignees,
|
||||
createdByActor: task.created_by_actor ?? null,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioGetNoteParams, AttioGetNoteResponse } from './types'
|
||||
import { NOTE_OUTPUT_PROPERTIES } from './types'
|
||||
import { mapNoteTags, NOTE_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioGetNote')
|
||||
|
||||
@@ -56,7 +56,7 @@ export const attioGetNoteTool: ToolConfig<AttioGetNoteParams, AttioGetNoteRespon
|
||||
contentPlaintext: note.content_plaintext ?? null,
|
||||
contentMarkdown: note.content_markdown ?? null,
|
||||
meetingId: note.meeting_id ?? null,
|
||||
tags: note.tags ?? [],
|
||||
tags: mapNoteTags(note.tags),
|
||||
createdByActor: note.created_by_actor ?? null,
|
||||
createdAt: note.created_at ?? null,
|
||||
},
|
||||
|
||||
@@ -36,7 +36,6 @@ export const attioGetTaskTool: ToolConfig<AttioGetTaskParams, AttioGetTaskRespon
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -66,6 +65,7 @@ export const attioGetTaskTool: ToolConfig<AttioGetTaskParams, AttioGetTaskRespon
|
||||
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,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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'
|
||||
@@ -13,6 +14,7 @@ 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'
|
||||
@@ -23,6 +25,7 @@ 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'
|
||||
@@ -33,6 +36,7 @@ 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'
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { AttioListNotesParams, AttioListNotesResponse } from './types'
|
||||
import { NOTE_OUTPUT_PROPERTIES } from './types'
|
||||
import { mapNoteTags, NOTE_OUTPUT_PROPERTIES } from './types'
|
||||
|
||||
const logger = createLogger('AttioListNotes')
|
||||
|
||||
@@ -81,7 +81,7 @@ export const attioListNotesTool: ToolConfig<AttioListNotesParams, AttioListNotes
|
||||
contentPlaintext: (note.content_plaintext as string) ?? null,
|
||||
contentMarkdown: (note.content_markdown as string) ?? null,
|
||||
meetingId: (note.meeting_id as string) ?? null,
|
||||
tags: (note.tags as unknown[]) ?? [],
|
||||
tags: mapNoteTags(note.tags),
|
||||
createdByActor: note.created_by_actor ?? null,
|
||||
createdAt: (note.created_at as string) ?? null,
|
||||
}
|
||||
|
||||
@@ -68,14 +68,14 @@ export const attioListRecordsTool: ToolConfig<AttioListRecordsParams, AttioListR
|
||||
try {
|
||||
body.filter = JSON.parse(params.filter)
|
||||
} catch {
|
||||
body.filter = params.filter
|
||||
throw new Error('Invalid JSON provided for filter')
|
||||
}
|
||||
}
|
||||
if (params.sorts) {
|
||||
try {
|
||||
body.sorts = JSON.parse(params.sorts)
|
||||
} catch {
|
||||
body.sorts = params.sorts
|
||||
throw new Error('Invalid JSON provided for sorts')
|
||||
}
|
||||
}
|
||||
if (params.limit != null) body.limit = params.limit
|
||||
|
||||
@@ -51,7 +51,8 @@ export const attioListTasksTool: ToolConfig<AttioListTasksParams, AttioListTasks
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: created_at:asc or created_at:desc',
|
||||
description:
|
||||
'Sort order: created_at:asc, created_at:desc, completed_at:asc, or completed_at:desc',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
@@ -118,6 +119,7 @@ export const attioListTasksTool: ToolConfig<AttioListTasksParams, AttioListTasks
|
||||
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,
|
||||
|
||||
+190
-37
@@ -1,5 +1,31 @@
|
||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
||||
|
||||
/** Raw Attio note tag shape (workspace-member or record variant) */
|
||||
export interface AttioNoteTag {
|
||||
type?: string
|
||||
workspace_member_id?: string
|
||||
object?: string
|
||||
record_id?: string
|
||||
}
|
||||
|
||||
/** Camelcase note tag shape returned in tool outputs */
|
||||
export interface NoteTagOutput {
|
||||
type: string | null
|
||||
workspaceMemberId: string | null
|
||||
object: string | null
|
||||
recordId: string | null
|
||||
}
|
||||
|
||||
/** Maps a raw Attio note tag to the camelCase output shape declared in NOTE_OUTPUT_PROPERTIES */
|
||||
export function mapNoteTags(tags: unknown): NoteTagOutput[] {
|
||||
return ((tags as AttioNoteTag[] | undefined) ?? []).map((tag) => ({
|
||||
type: tag.type ?? null,
|
||||
workspaceMemberId: tag.workspace_member_id ?? null,
|
||||
object: tag.object ?? null,
|
||||
recordId: tag.record_id ?? null,
|
||||
}))
|
||||
}
|
||||
|
||||
/** Reusable actor shape returned by the Attio API */
|
||||
export const ACTOR_OUTPUT_PROPERTIES = {
|
||||
type: {
|
||||
@@ -70,8 +96,22 @@ export const NOTE_OUTPUT_PROPERTIES = {
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: { type: 'string', description: 'The tag type (e.g. workspace-member)' },
|
||||
workspaceMemberId: { type: 'string', description: 'The workspace member ID of the tagger' },
|
||||
type: { type: 'string', description: 'The tag type (workspace-member or record)' },
|
||||
workspaceMemberId: {
|
||||
type: 'string',
|
||||
description: 'The workspace member ID (present when type is workspace-member)',
|
||||
optional: true,
|
||||
},
|
||||
object: {
|
||||
type: 'string',
|
||||
description: 'The tagged object slug (present when type is record)',
|
||||
optional: true,
|
||||
},
|
||||
recordId: {
|
||||
type: 'string',
|
||||
description: 'The tagged record ID (present when type is record)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -89,6 +129,7 @@ export const TASK_OUTPUT_PROPERTIES = {
|
||||
content: { type: 'string', description: 'The task content' },
|
||||
deadlineAt: { type: 'string', description: 'The task deadline', optional: true },
|
||||
isCompleted: { type: 'boolean', description: 'Whether the task is completed' },
|
||||
completedAt: { type: 'string', description: 'When the task was completed', optional: true },
|
||||
linkedRecords: {
|
||||
type: 'array',
|
||||
description: 'Records linked to this task',
|
||||
@@ -162,6 +203,43 @@ export const MEMBER_OUTPUT_PROPERTIES = {
|
||||
createdAt: { type: 'string', description: 'When the member was added' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/** Shared output properties for Attio attributes */
|
||||
export const ATTRIBUTE_OUTPUT_PROPERTIES = {
|
||||
attributeId: { type: 'string', description: 'The attribute ID' },
|
||||
title: { type: 'string', description: 'The attribute display title' },
|
||||
apiSlug: { type: 'string', description: 'The attribute API slug' },
|
||||
description: { type: 'string', description: 'The attribute description', optional: true },
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'The attribute value type (e.g. text, number, select, record-reference)',
|
||||
},
|
||||
isSystemAttribute: {
|
||||
type: 'boolean',
|
||||
description: 'Whether this is a built-in system attribute',
|
||||
},
|
||||
isWritable: { type: 'boolean', description: 'Whether the attribute can be written to' },
|
||||
isRequired: { type: 'boolean', description: 'Whether new records must provide a value' },
|
||||
isUnique: { type: 'boolean', description: 'Whether the attribute enforces uniqueness' },
|
||||
isMultiselect: { type: 'boolean', description: 'Whether the attribute supports multiple values' },
|
||||
isDefaultValueEnabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether this attribute has a default value enabled',
|
||||
},
|
||||
isArchived: { type: 'boolean', description: 'Whether the attribute is archived' },
|
||||
defaultValue: {
|
||||
type: 'json',
|
||||
description: 'The default value for this attribute, if enabled',
|
||||
optional: true,
|
||||
},
|
||||
relationship: {
|
||||
type: 'json',
|
||||
description: 'The related attribute, if this attribute is part of a relationship',
|
||||
optional: true,
|
||||
},
|
||||
config: { type: 'json', description: 'Type-dependent attribute configuration', optional: true },
|
||||
createdAt: { type: 'string', description: 'When the attribute was created' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/** Shared output properties for Attio comments */
|
||||
export const COMMENT_OUTPUT_PROPERTIES = {
|
||||
commentId: { type: 'string', description: 'The comment ID' },
|
||||
@@ -248,32 +326,6 @@ interface AttioRecord {
|
||||
values: Record<string, unknown>
|
||||
}
|
||||
|
||||
/** Raw Attio note shape from the API */
|
||||
interface AttioNote {
|
||||
id: { workspace_id: string; note_id: string }
|
||||
parent_object: string
|
||||
parent_record_id: string
|
||||
title: string
|
||||
content_plaintext: string
|
||||
content_markdown: string
|
||||
meeting_id: string | null
|
||||
tags: unknown[]
|
||||
created_by_actor: unknown
|
||||
created_at: string
|
||||
}
|
||||
|
||||
/** Raw Attio task shape from the API */
|
||||
interface AttioTask {
|
||||
id: { workspace_id: string; task_id: string }
|
||||
content_plaintext: string
|
||||
deadline_at: string | null
|
||||
is_completed: boolean
|
||||
linked_records: Array<{ target_object_id: string; target_record_id: string }>
|
||||
assignees: Array<{ referenced_actor_type: string; referenced_actor_id: string }>
|
||||
created_by_actor: unknown
|
||||
created_at: string
|
||||
}
|
||||
|
||||
/** Params for listing/querying records */
|
||||
export interface AttioListRecordsParams {
|
||||
accessToken: string
|
||||
@@ -453,7 +505,7 @@ export interface AttioListNotesResponse extends ToolResponse {
|
||||
contentPlaintext: string | null
|
||||
contentMarkdown: string | null
|
||||
meetingId: string | null
|
||||
tags: unknown[]
|
||||
tags: NoteTagOutput[]
|
||||
createdByActor: unknown
|
||||
createdAt: string | null
|
||||
}>
|
||||
@@ -471,7 +523,7 @@ export interface AttioCreateNoteResponse extends ToolResponse {
|
||||
contentPlaintext: string | null
|
||||
contentMarkdown: string | null
|
||||
meetingId: string | null
|
||||
tags: unknown[]
|
||||
tags: NoteTagOutput[]
|
||||
createdByActor: unknown
|
||||
createdAt: string | null
|
||||
}
|
||||
@@ -492,6 +544,7 @@ export interface AttioListTasksResponse extends ToolResponse {
|
||||
content: string | null
|
||||
deadlineAt: string | null
|
||||
isCompleted: boolean
|
||||
completedAt: string | null
|
||||
linkedRecords: Array<{ targetObjectId: string | null; targetRecordId: string | null }>
|
||||
assignees: Array<{ type: string | null; id: string | null }>
|
||||
createdByActor: unknown
|
||||
@@ -508,6 +561,7 @@ export interface AttioCreateTaskResponse extends ToolResponse {
|
||||
content: string | null
|
||||
deadlineAt: string | null
|
||||
isCompleted: boolean
|
||||
completedAt: string | null
|
||||
linkedRecords: Array<{ targetObjectId: string | null; targetRecordId: string | null }>
|
||||
assignees: Array<{ type: string | null; id: string | null }>
|
||||
createdByActor: unknown
|
||||
@@ -522,6 +576,7 @@ export interface AttioUpdateTaskResponse extends ToolResponse {
|
||||
content: string | null
|
||||
deadlineAt: string | null
|
||||
isCompleted: boolean
|
||||
completedAt: string | null
|
||||
linkedRecords: Array<{ targetObjectId: string | null; targetRecordId: string | null }>
|
||||
assignees: Array<{ type: string | null; id: string | null }>
|
||||
createdByActor: unknown
|
||||
@@ -542,6 +597,7 @@ export interface AttioGetTaskResponse extends ToolResponse {
|
||||
content: string | null
|
||||
deadlineAt: string | null
|
||||
isCompleted: boolean
|
||||
completedAt: string | null
|
||||
linkedRecords: Array<{ targetObjectId: string | null; targetRecordId: string | null }>
|
||||
assignees: Array<{ type: string | null; id: string | null }>
|
||||
createdByActor: unknown
|
||||
@@ -572,7 +628,7 @@ export interface AttioGetNoteResponse extends ToolResponse {
|
||||
contentPlaintext: string | null
|
||||
contentMarkdown: string | null
|
||||
meetingId: string | null
|
||||
tags: unknown[]
|
||||
tags: NoteTagOutput[]
|
||||
createdByActor: unknown
|
||||
createdAt: string | null
|
||||
}
|
||||
@@ -684,7 +740,7 @@ export interface AttioListListsResponse extends ToolResponse {
|
||||
name: string | null
|
||||
parentObject: string | null
|
||||
workspaceAccess: string | null
|
||||
workspaceMemberAccess: string | null
|
||||
workspaceMemberAccess: unknown
|
||||
createdByActor: { type: string | null; id: string | null } | null
|
||||
createdAt: string | null
|
||||
}>
|
||||
@@ -706,7 +762,7 @@ export interface AttioGetListResponse extends ToolResponse {
|
||||
name: string | null
|
||||
parentObject: string | null
|
||||
workspaceAccess: string | null
|
||||
workspaceMemberAccess: string | null
|
||||
workspaceMemberAccess: unknown
|
||||
createdByActor: { type: string | null; id: string | null } | null
|
||||
createdAt: string | null
|
||||
}
|
||||
@@ -730,7 +786,7 @@ export interface AttioCreateListResponse extends ToolResponse {
|
||||
name: string | null
|
||||
parentObject: string | null
|
||||
workspaceAccess: string | null
|
||||
workspaceMemberAccess: string | null
|
||||
workspaceMemberAccess: unknown
|
||||
createdByActor: { type: string | null; id: string | null } | null
|
||||
createdAt: string | null
|
||||
}
|
||||
@@ -754,7 +810,7 @@ export interface AttioUpdateListResponse extends ToolResponse {
|
||||
name: string | null
|
||||
parentObject: string | null
|
||||
workspaceAccess: string | null
|
||||
workspaceMemberAccess: string | null
|
||||
workspaceMemberAccess: unknown
|
||||
createdByActor: { type: string | null; id: string | null } | null
|
||||
createdAt: string | null
|
||||
}
|
||||
@@ -906,8 +962,10 @@ export interface AttioCreateCommentParams {
|
||||
format?: string
|
||||
authorType: string
|
||||
authorId: string
|
||||
list: string
|
||||
entryId: string
|
||||
list?: string
|
||||
entryId?: string
|
||||
recordObject?: string
|
||||
recordId?: string
|
||||
threadId?: string
|
||||
createdAt?: string
|
||||
}
|
||||
@@ -1098,6 +1156,97 @@ export interface AttioDeleteWebhookResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/** Params for listing attributes on an object or list */
|
||||
export interface AttioListAttributesParams {
|
||||
accessToken: string
|
||||
target: string
|
||||
identifier: string
|
||||
limit?: number
|
||||
offset?: number
|
||||
showArchived?: boolean
|
||||
}
|
||||
|
||||
/** Attribute shape as returned in tool outputs (camelCase) */
|
||||
interface AttioAttributeOutput {
|
||||
attributeId: string | null
|
||||
title: string | null
|
||||
apiSlug: string | null
|
||||
description: string | null
|
||||
type: string | null
|
||||
isSystemAttribute: boolean
|
||||
isWritable: boolean
|
||||
isRequired: boolean
|
||||
isUnique: boolean
|
||||
isMultiselect: boolean
|
||||
isDefaultValueEnabled: boolean
|
||||
isArchived: boolean
|
||||
defaultValue: Record<string, unknown> | null
|
||||
relationship: Record<string, unknown> | null
|
||||
config: Record<string, unknown> | null
|
||||
createdAt: string | null
|
||||
}
|
||||
|
||||
/** Response for listing attributes */
|
||||
export interface AttioListAttributesResponse extends ToolResponse {
|
||||
output: {
|
||||
attributes: AttioAttributeOutput[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
/** Params for getting a single attribute */
|
||||
export interface AttioGetAttributeParams {
|
||||
accessToken: string
|
||||
target: string
|
||||
identifier: string
|
||||
attribute: string
|
||||
}
|
||||
|
||||
/** Response for getting a single attribute */
|
||||
export interface AttioGetAttributeResponse extends ToolResponse {
|
||||
output: AttioAttributeOutput
|
||||
}
|
||||
|
||||
/** Params for creating an attribute */
|
||||
export interface AttioCreateAttributeParams {
|
||||
accessToken: string
|
||||
target: string
|
||||
identifier: string
|
||||
title: string
|
||||
apiSlug: string
|
||||
type: string
|
||||
description?: string
|
||||
isRequired?: boolean
|
||||
isUnique?: boolean
|
||||
isMultiselect?: boolean
|
||||
config?: string
|
||||
}
|
||||
|
||||
/** Response for creating an attribute */
|
||||
export interface AttioCreateAttributeResponse extends ToolResponse {
|
||||
output: AttioAttributeOutput
|
||||
}
|
||||
|
||||
/** Params for updating an attribute */
|
||||
export interface AttioUpdateAttributeParams {
|
||||
accessToken: string
|
||||
target: string
|
||||
identifier: string
|
||||
attribute: string
|
||||
title?: string
|
||||
apiSlug?: string
|
||||
description?: string
|
||||
isRequired?: boolean
|
||||
isUnique?: boolean
|
||||
isArchived?: boolean
|
||||
config?: string
|
||||
}
|
||||
|
||||
/** Response for updating an attribute */
|
||||
export interface AttioUpdateAttributeResponse extends ToolResponse {
|
||||
output: AttioAttributeOutput
|
||||
}
|
||||
|
||||
export type AttioResponse =
|
||||
| AttioListRecordsResponse
|
||||
| AttioGetRecordResponse
|
||||
@@ -1140,3 +1289,7 @@ export type AttioResponse =
|
||||
| AttioCreateWebhookResponse
|
||||
| AttioUpdateWebhookResponse
|
||||
| AttioDeleteWebhookResponse
|
||||
| AttioListAttributesResponse
|
||||
| AttioGetAttributeResponse
|
||||
| AttioCreateAttributeResponse
|
||||
| AttioUpdateAttributeResponse
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export const attioUpdateRecordTool: ToolConfig<AttioUpdateRecordParams, AttioUpd
|
||||
try {
|
||||
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
|
||||
} catch {
|
||||
values = {}
|
||||
throw new Error('Invalid JSON provided for record values')
|
||||
}
|
||||
return { data: { values } }
|
||||
},
|
||||
|
||||
@@ -114,6 +114,7 @@ export const attioUpdateTaskTool: ToolConfig<AttioUpdateTaskParams, AttioUpdateT
|
||||
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,
|
||||
|
||||
@@ -257,6 +257,7 @@ import {
|
||||
} from '@/tools/athena'
|
||||
import {
|
||||
attioAssertRecordTool,
|
||||
attioCreateAttributeTool,
|
||||
attioCreateCommentTool,
|
||||
attioCreateListEntryTool,
|
||||
attioCreateListTool,
|
||||
@@ -271,6 +272,7 @@ import {
|
||||
attioDeleteRecordTool,
|
||||
attioDeleteTaskTool,
|
||||
attioDeleteWebhookTool,
|
||||
attioGetAttributeTool,
|
||||
attioGetCommentTool,
|
||||
attioGetListEntryTool,
|
||||
attioGetListTool,
|
||||
@@ -281,6 +283,7 @@ import {
|
||||
attioGetTaskTool,
|
||||
attioGetThreadTool,
|
||||
attioGetWebhookTool,
|
||||
attioListAttributesTool,
|
||||
attioListListsTool,
|
||||
attioListMembersTool,
|
||||
attioListNotesTool,
|
||||
@@ -291,6 +294,7 @@ import {
|
||||
attioListWebhooksTool,
|
||||
attioQueryListEntriesTool,
|
||||
attioSearchRecordsTool,
|
||||
attioUpdateAttributeTool,
|
||||
attioUpdateListEntryTool,
|
||||
attioUpdateListTool,
|
||||
attioUpdateObjectTool,
|
||||
@@ -6798,6 +6802,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
airtable_update_record: airtableUpdateRecordTool,
|
||||
airtable_upsert_records: airtableUpsertRecordsTool,
|
||||
attio_assert_record: attioAssertRecordTool,
|
||||
attio_create_attribute: attioCreateAttributeTool,
|
||||
attio_create_comment: attioCreateCommentTool,
|
||||
attio_create_list: attioCreateListTool,
|
||||
attio_create_list_entry: attioCreateListEntryTool,
|
||||
@@ -6812,6 +6817,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
attio_delete_record: attioDeleteRecordTool,
|
||||
attio_delete_task: attioDeleteTaskTool,
|
||||
attio_delete_webhook: attioDeleteWebhookTool,
|
||||
attio_get_attribute: attioGetAttributeTool,
|
||||
attio_get_comment: attioGetCommentTool,
|
||||
attio_get_list: attioGetListTool,
|
||||
attio_get_list_entry: attioGetListEntryTool,
|
||||
@@ -6822,6 +6828,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
attio_get_task: attioGetTaskTool,
|
||||
attio_get_thread: attioGetThreadTool,
|
||||
attio_get_webhook: attioGetWebhookTool,
|
||||
attio_list_attributes: attioListAttributesTool,
|
||||
attio_list_lists: attioListListsTool,
|
||||
attio_list_members: attioListMembersTool,
|
||||
attio_list_notes: attioListNotesTool,
|
||||
@@ -6832,6 +6839,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
attio_list_webhooks: attioListWebhooksTool,
|
||||
attio_query_list_entries: attioQueryListEntriesTool,
|
||||
attio_search_records: attioSearchRecordsTool,
|
||||
attio_update_attribute: attioUpdateAttributeTool,
|
||||
attio_update_list: attioUpdateListTool,
|
||||
attio_update_list_entry: attioUpdateListEntryTool,
|
||||
attio_update_object: attioUpdateObjectTool,
|
||||
|
||||
Reference in New Issue
Block a user