From 11c7a36f1125d887492b758d0e5e1ed6a59ef60f Mon Sep 17 00:00:00 2001 From: Waleed Date: Thu, 2 Jul 2026 11:14:14 -0700 Subject: [PATCH] feat(hex): expand API coverage, fix ID trimming, add cursor pagination (#5372) * feat(hex): expand API coverage, fix ID trimming, add cursor pagination - Add hex_update_collection, hex_create_group, hex_update_group, hex_delete_group, hex_deactivate_user tools + block wiring - Add missing run_project fields (viewId, notifications) and get_project_runs runTriggerFilter - Add after/before cursor pagination to list_projects, list_groups, list_data_connections, list_collections - Add .trim() on all interpolated ID path params to guard against copy-paste whitespace * fix(hex): guard JSON.parse on user-supplied array/object params Wrap JSON.parse calls for memberUserIds, addUserIds, removeUserIds, inputParams, and notifications in try/catch so malformed input throws a clear error instead of an opaque parse exception. * fix(hex): forward empty collection description on update update_collection's params() mapping only copied collectionDescription when truthy, so clearing it to an empty string in the UI never reached the API. Untouched fields resolve to null (not undefined), so use a loose null check to distinguish "cleared" from "never touched" without sending description: null on every unrelated update. * fix(hex): close remaining API coverage gaps found via raw OpenAPI spec Pulled Hex's actual OpenAPI spec (static.hex.site/openapi.json) as ground truth for a final verification pass: - list_users was missing after/before cursor pagination and the userIds filter, which the spec confirms it supports (an earlier doc-summary pass had incorrectly flagged this as unverifiable) - list_users response also exposes lastLoginDate per user, not previously surfaced - list_projects was missing includeComponents, includeTrashed, creatorEmail, ownerEmail, collectionId, categories, sortBy, and sortDirection filters that the spec confirms are real query params * fix(hex): trim remaining user-suppliable IDs for consistency Trim collectionId (list_projects filter), viewId (run_project body), and group member UUIDs (create_group/update_group) to match the .trim() convention already applied to path-segment IDs elsewhere. * fix(hex): validate parsed JSON is actually an array before iterating categories, memberUserIds, addUserIds, and removeUserIds could parse as valid JSON that isn't an array (e.g. an object), which would throw an opaque "not iterable"/"map is not a function" error deeper in the call. Validate Array.isArray after parsing and fail with a clear message instead. * fix(hex): validate array element types, send explicit ALL trigger filter - notifications now gets the same Array.isArray guard already applied to categories/memberUserIds/addUserIds/removeUserIds - member/category array elements are now validated as strings before .trim()/append, instead of crashing on non-string entries - runTriggerFilter "All" option now sends the documented ALL enum value explicitly instead of relying on omission --- apps/sim/blocks/blocks/hex.ts | 336 +++++++++++++++++++- apps/sim/tools/hex/cancel_run.ts | 2 +- apps/sim/tools/hex/create_group.ts | 78 +++++ apps/sim/tools/hex/deactivate_user.ts | 60 ++++ apps/sim/tools/hex/delete_group.ts | 60 ++++ apps/sim/tools/hex/get_collection.ts | 2 +- apps/sim/tools/hex/get_data_connection.ts | 3 +- apps/sim/tools/hex/get_group.ts | 2 +- apps/sim/tools/hex/get_project.ts | 2 +- apps/sim/tools/hex/get_project_runs.ts | 17 +- apps/sim/tools/hex/get_queried_tables.ts | 2 +- apps/sim/tools/hex/get_run_status.ts | 2 +- apps/sim/tools/hex/index.ts | 10 + apps/sim/tools/hex/list_collections.ts | 22 ++ apps/sim/tools/hex/list_data_connections.ts | 22 ++ apps/sim/tools/hex/list_groups.ts | 22 ++ apps/sim/tools/hex/list_projects.ts | 94 ++++++ apps/sim/tools/hex/list_users.ts | 35 ++ apps/sim/tools/hex/run_project.ts | 43 ++- apps/sim/tools/hex/types.ts | 110 +++++++ apps/sim/tools/hex/update_collection.ts | 85 +++++ apps/sim/tools/hex/update_group.ts | 106 ++++++ apps/sim/tools/hex/update_project.ts | 2 +- apps/sim/tools/registry.ts | 10 + 24 files changed, 1101 insertions(+), 26 deletions(-) create mode 100644 apps/sim/tools/hex/create_group.ts create mode 100644 apps/sim/tools/hex/deactivate_user.ts create mode 100644 apps/sim/tools/hex/delete_group.ts create mode 100644 apps/sim/tools/hex/update_collection.ts create mode 100644 apps/sim/tools/hex/update_group.ts diff --git a/apps/sim/blocks/blocks/hex.ts b/apps/sim/blocks/blocks/hex.ts index 2bb36f75ef..76cf338782 100644 --- a/apps/sim/blocks/blocks/hex.ts +++ b/apps/sim/blocks/blocks/hex.ts @@ -8,7 +8,7 @@ export const HexBlock: BlockConfig = { name: 'Hex', description: 'Run and manage Hex projects', longDescription: - 'Integrate Hex into your workflow. Run projects, check run status, manage collections and groups, list users, and view data connections. Requires a Hex API token.', + 'Integrate Hex into your workflow. Run projects, check run status, manage collections and groups (including membership and deactivating users), list users, and view data connections. Requires a Hex API token.', docsLink: 'https://docs.sim.ai/integrations/hex', category: 'tools', integrationType: IntegrationType.Analytics, @@ -36,8 +36,13 @@ export const HexBlock: BlockConfig = { { label: 'List Collections', id: 'list_collections' }, { label: 'Get Collection', id: 'get_collection' }, { label: 'Create Collection', id: 'create_collection' }, + { label: 'Update Collection', id: 'update_collection' }, { label: 'List Data Connections', id: 'list_data_connections' }, { label: 'Get Data Connection', id: 'get_data_connection' }, + { label: 'Create Group', id: 'create_group' }, + { label: 'Update Group', id: 'update_group' }, + { label: 'Delete Group', id: 'delete_group' }, + { label: 'Deactivate User', id: 'deactivate_user' }, ], value: () => 'run_project', }, @@ -107,6 +112,39 @@ Example: generationType: 'json-object', }, }, + { + id: 'viewId', + title: 'Saved View ID', + type: 'short-input', + placeholder: 'Enter a SavedView UUID (optional)', + condition: { field: 'operation', value: 'run_project' }, + mode: 'advanced', + }, + { + id: 'notifications', + title: 'Notifications', + type: 'code', + placeholder: '[{"type": "FAILURE", "slackChannelIds": ["C0123456789"]}]', + condition: { field: 'operation', value: 'run_project' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating Hex run notification configs. +Generate ONLY the raw JSON array based on the user's request. +The output MUST be a single, valid JSON array, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array. +Each item's "type" must be one of ALL, SUCCESS, or FAILURE. Optional fields: includeSuccessScreenshot (boolean), slackChannelIds, userIds, groupIds (arrays of strings). + +Example: +[{"type": "FAILURE", "slackChannelIds": ["C0123456789"], "includeSuccessScreenshot": false}]`, + placeholder: 'Describe who should be notified and when...', + generationType: 'json-object', + }, + }, { id: 'projectStatus', title: 'Status', @@ -130,28 +168,119 @@ Example: value: () => '', condition: { field: 'operation', value: 'get_project_runs' }, }, + { + id: 'runTriggerFilter', + title: 'Trigger Filter', + type: 'dropdown', + options: [ + { label: 'All', id: 'ALL' }, + { label: 'API', id: 'API' }, + { label: 'Scheduled', id: 'SCHEDULED' }, + { label: 'App Refresh', id: 'APP_REFRESH' }, + ], + value: () => 'ALL', + condition: { field: 'operation', value: 'get_project_runs' }, + mode: 'advanced', + }, { id: 'groupIdInput', title: 'Group ID', type: 'short-input', placeholder: 'Enter group UUID', - condition: { field: 'operation', value: 'get_group' }, - required: { field: 'operation', value: 'get_group' }, + condition: { field: 'operation', value: ['get_group', 'update_group', 'delete_group'] }, + required: { field: 'operation', value: ['get_group', 'update_group', 'delete_group'] }, + }, + { + id: 'groupName', + title: 'Group Name', + type: 'short-input', + placeholder: 'Enter group name', + condition: { field: 'operation', value: ['create_group', 'update_group'] }, + required: { field: 'operation', value: 'create_group' }, + }, + { + id: 'groupMemberUserIds', + title: 'Initial Member User IDs', + type: 'code', + placeholder: '["uuid1", "uuid2"]', + condition: { field: 'operation', value: 'create_group' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating JSON arrays of user UUIDs. +Generate ONLY the raw JSON array of user ID strings based on the user's request. +The output MUST be a single, valid JSON array of strings, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array. + +Example: +["a1b2c3d4-0000-0000-0000-000000000000", "e5f6a7b8-0000-0000-0000-000000000000"]`, + placeholder: 'Describe which users to add...', + generationType: 'json-object', + }, + }, + { + id: 'groupAddUserIds', + title: 'Add Member User IDs', + type: 'code', + placeholder: '["uuid1", "uuid2"]', + condition: { field: 'operation', value: 'update_group' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating JSON arrays of user UUIDs. +Generate ONLY the raw JSON array of user ID strings to add based on the user's request. +The output MUST be a single, valid JSON array of strings, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array.`, + placeholder: 'Describe which users to add...', + generationType: 'json-object', + }, + }, + { + id: 'groupRemoveUserIds', + title: 'Remove Member User IDs', + type: 'code', + placeholder: '["uuid1", "uuid2"]', + condition: { field: 'operation', value: 'update_group' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating JSON arrays of user UUIDs. +Generate ONLY the raw JSON array of user ID strings to remove based on the user's request. +The output MUST be a single, valid JSON array of strings, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array.`, + placeholder: 'Describe which users to remove...', + generationType: 'json-object', + }, }, { id: 'collectionId', title: 'Collection ID', type: 'short-input', placeholder: 'Enter collection UUID', - condition: { field: 'operation', value: 'get_collection' }, - required: { field: 'operation', value: 'get_collection' }, + condition: { + field: 'operation', + value: ['get_collection', 'update_collection', 'list_projects'], + }, + required: { field: 'operation', value: ['get_collection', 'update_collection'] }, }, { id: 'collectionName', title: 'Collection Name', type: 'short-input', placeholder: 'Enter collection name', - condition: { field: 'operation', value: 'create_collection' }, + condition: { field: 'operation', value: ['create_collection', 'update_collection'] }, required: { field: 'operation', value: 'create_collection' }, }, { @@ -159,7 +288,7 @@ Example: title: 'Description', type: 'long-input', placeholder: 'Optional description for the collection', - condition: { field: 'operation', value: 'create_collection' }, + condition: { field: 'operation', value: ['create_collection', 'update_collection'] }, }, { id: 'dataConnectionId', @@ -169,6 +298,14 @@ Example: condition: { field: 'operation', value: 'get_data_connection' }, required: { field: 'operation', value: 'get_data_connection' }, }, + { + id: 'userId', + title: 'User ID', + type: 'short-input', + placeholder: 'Enter user UUID', + condition: { field: 'operation', value: 'deactivate_user' }, + required: { field: 'operation', value: 'deactivate_user' }, + }, { id: 'apiKey', title: 'API Key', @@ -240,6 +377,20 @@ Example: condition: { field: 'operation', value: 'list_projects' }, mode: 'advanced', }, + { + id: 'includeComponents', + title: 'Include Components', + type: 'switch', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'includeTrashed', + title: 'Include Trashed', + type: 'switch', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, { id: 'statusFilter', title: 'Status Filter', @@ -253,6 +404,57 @@ Example: condition: { field: 'operation', value: 'list_projects' }, mode: 'advanced', }, + { + id: 'creatorEmail', + title: 'Creator Email', + type: 'short-input', + placeholder: 'Filter by creator email (optional)', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'ownerEmail', + title: 'Owner Email', + type: 'short-input', + placeholder: 'Filter by owner email (optional)', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'categories', + title: 'Categories', + type: 'code', + placeholder: '["Marketing", "Finance"]', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'sortBy', + title: 'Sort By', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Created At', id: 'CREATED_AT' }, + { label: 'Last Edited At', id: 'LAST_EDITED_AT' }, + { label: 'Last Published At', id: 'LAST_PUBLISHED_AT' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'sortDirection', + title: 'Sort Direction', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Ascending', id: 'ASC' }, + { label: 'Descending', id: 'DESC' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, { id: 'groupId', title: 'Filter by Group', @@ -261,12 +463,57 @@ Example: condition: { field: 'operation', value: 'list_users' }, mode: 'advanced', }, + { + id: 'userIds', + title: 'Filter by User IDs', + type: 'short-input', + placeholder: 'Comma-separated user UUIDs (optional)', + condition: { field: 'operation', value: 'list_users' }, + mode: 'advanced', + }, + { + id: 'after', + title: 'After Cursor', + type: 'short-input', + placeholder: 'Cursor for the next page', + condition: { + field: 'operation', + value: [ + 'list_projects', + 'list_groups', + 'list_collections', + 'list_data_connections', + 'list_users', + ], + }, + mode: 'advanced', + }, + { + id: 'before', + title: 'Before Cursor', + type: 'short-input', + placeholder: 'Cursor for the previous page', + condition: { + field: 'operation', + value: [ + 'list_projects', + 'list_groups', + 'list_collections', + 'list_data_connections', + 'list_users', + ], + }, + mode: 'advanced', + }, ], tools: { access: [ 'hex_cancel_run', 'hex_create_collection', + 'hex_create_group', + 'hex_deactivate_user', + 'hex_delete_group', 'hex_get_collection', 'hex_get_data_connection', 'hex_get_group', @@ -280,6 +527,8 @@ Example: 'hex_list_projects', 'hex_list_users', 'hex_run_project', + 'hex_update_collection', + 'hex_update_group', 'hex_update_project', ], config: { @@ -313,10 +562,20 @@ Example: return 'hex_get_collection' case 'create_collection': return 'hex_create_collection' + case 'update_collection': + return 'hex_update_collection' case 'list_data_connections': return 'hex_list_data_connections' case 'get_data_connection': return 'hex_get_data_connection' + case 'create_group': + return 'hex_create_group' + case 'update_group': + return 'hex_update_group' + case 'delete_group': + return 'hex_delete_group' + case 'deactivate_user': + return 'hex_deactivate_user' default: return 'hex_run_project' } @@ -330,11 +589,26 @@ Example: if (op === 'update_project' && params.projectStatus) result.status = params.projectStatus if (op === 'get_project_runs' && params.runStatusFilter) result.statusFilter = params.runStatusFilter - if (op === 'get_group' && params.groupIdInput) result.groupId = params.groupIdInput + if ( + (op === 'get_group' || op === 'update_group' || op === 'delete_group') && + params.groupIdInput + ) + result.groupId = params.groupIdInput if (op === 'list_users' && params.groupId) result.groupId = params.groupId - if (op === 'create_collection' && params.collectionName) result.name = params.collectionName + if ((op === 'create_collection' || op === 'update_collection') && params.collectionName) + result.name = params.collectionName if (op === 'create_collection' && params.collectionDescription) result.description = params.collectionDescription + if (op === 'update_collection' && params.collectionDescription != null) + result.description = params.collectionDescription + if ((op === 'create_group' || op === 'update_group') && params.groupName) + result.name = params.groupName + if (op === 'create_group' && params.groupMemberUserIds) + result.memberUserIds = params.groupMemberUserIds + if (op === 'update_group' && params.groupAddUserIds) + result.addUserIds = params.groupAddUserIds + if (op === 'update_group' && params.groupRemoveUserIds) + result.removeUserIds = params.groupRemoveUserIds return result }, @@ -360,21 +634,42 @@ Example: type: 'boolean', description: 'Use cached SQL results instead of re-running queries', }, + viewId: { type: 'string', description: 'SavedView UUID to use for the project run' }, + notifications: { + type: 'json', + description: 'Notification details to deliver once the run completes', + }, projectStatus: { type: 'string', description: 'New project status name (custom workspace status label)', }, limit: { type: 'number', description: 'Max number of results to return' }, offset: { type: 'number', description: 'Offset for paginated results' }, + after: { type: 'string', description: 'Cursor to fetch results after' }, + before: { type: 'string', description: 'Cursor to fetch results before' }, includeArchived: { type: 'boolean', description: 'Include archived projects' }, + includeComponents: { type: 'boolean', description: 'Include components in results' }, + includeTrashed: { type: 'boolean', description: 'Include trashed projects in results' }, statusFilter: { type: 'string', description: 'Filter projects by status' }, + creatorEmail: { type: 'string', description: 'Filter projects by creator email' }, + ownerEmail: { type: 'string', description: 'Filter projects by owner email' }, + categories: { type: 'json', description: 'Filter projects by category names' }, + sortBy: { type: 'string', description: 'Sort field for list results' }, + sortDirection: { type: 'string', description: 'Sort direction for list results' }, runStatusFilter: { type: 'string', description: 'Filter runs by status' }, + runTriggerFilter: { type: 'string', description: 'Filter runs by trigger source' }, groupId: { type: 'string', description: 'Filter users by group UUID' }, - groupIdInput: { type: 'string', description: 'Group UUID for get group' }, + userIds: { type: 'string', description: 'Comma-separated user UUIDs to filter by' }, + groupIdInput: { type: 'string', description: 'Group UUID for get/update/delete group' }, + groupName: { type: 'string', description: 'Group name' }, + groupMemberUserIds: { type: 'json', description: 'Initial member user UUIDs for new group' }, + groupAddUserIds: { type: 'json', description: 'User UUIDs to add to the group' }, + groupRemoveUserIds: { type: 'json', description: 'User UUIDs to remove from the group' }, collectionId: { type: 'string', description: 'Collection UUID' }, collectionName: { type: 'string', description: 'Collection name' }, collectionDescription: { type: 'string', description: 'Collection description' }, dataConnectionId: { type: 'string', description: 'Data connection UUID' }, + userId: { type: 'string', description: 'User UUID' }, }, outputs: { @@ -415,7 +710,10 @@ Example: description: 'List of runs with runId, status, runUrl, startTime, endTime, elapsedTime, projectVersion', }, - users: { type: 'json', description: 'List of users with id, name, email, role' }, + users: { + type: 'json', + description: 'List of users with id, name, email, role, lastLoginDate', + }, groups: { type: 'json', description: 'List of groups with id, name, createdAt' }, collections: { type: 'json', @@ -437,8 +735,15 @@ Example: creator: { type: 'json', description: 'Creator details ({ email, id })' }, owner: { type: 'json', description: 'Owner details ({ email })' }, total: { type: 'number', description: 'Total results returned' }, - // Cancel output + // Cancel / delete / deactivate output success: { type: 'boolean', description: 'Whether the operation succeeded' }, + groupId: { type: 'string', description: 'Group UUID' }, + userId: { type: 'string', description: 'User UUID' }, + // Pagination + nextPage: { type: 'string', description: 'Cursor for the next page of runs' }, + previousPage: { type: 'string', description: 'Cursor for the previous page of runs' }, + after: { type: 'string', description: 'Cursor for the next page of results' }, + before: { type: 'string', description: 'Cursor for the previous page of results' }, // Data connection flags connectViaSsh: { type: 'boolean', description: 'SSH tunneling enabled' }, includeMagic: { type: 'boolean', description: 'Magic AI features enabled' }, @@ -546,5 +851,12 @@ export const HexBlockMeta = { content: '# Inventory Projects\n\nMap what projects and data sources exist in the workspace.\n\n## Steps\n1. List projects and capture IDs, names, and owners.\n2. List collections and get details to see how projects are grouped.\n3. List data connections to map which sources power the projects.\n4. Cross-reference projects to their collections and data connections.\n\n## Output\nReturn an inventory of projects grouped by collection, each annotated with its data connections. Useful for governance and cleanup.', }, + { + name: 'onboard-offboard-teammate', + description: + 'Add a new teammate to the right Hex groups on hire, or deactivate and remove them on departure.', + content: + '# Onboard/Offboard Teammate\n\nManage workspace access as people join or leave the team.\n\n## Steps\n1. List users to resolve the target user by name or email, and list groups to resolve the relevant group by name.\n2. For onboarding: add the user to the appropriate group(s) via group update.\n3. For offboarding: remove the user from their groups via group update, then deactivate the user account.\n4. Confirm the change by getting the group or listing users filtered by group.\n\n## Output\nReturn the user and group IDs affected and the action taken (added, removed, deactivated). Flag if the user or group could not be resolved.', + }, ], } as const satisfies BlockMeta diff --git a/apps/sim/tools/hex/cancel_run.ts b/apps/sim/tools/hex/cancel_run.ts index 17c65944c3..f7a9646b49 100644 --- a/apps/sim/tools/hex/cancel_run.ts +++ b/apps/sim/tools/hex/cancel_run.ts @@ -30,7 +30,7 @@ export const cancelRunTool: ToolConfig request: { url: (params) => - `https://app.hex.tech/api/v1/projects/${params.projectId}/runs/${params.runId}`, + `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}/runs/${params.runId.trim()}`, method: 'DELETE', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/create_group.ts b/apps/sim/tools/hex/create_group.ts new file mode 100644 index 0000000000..cac1e503b3 --- /dev/null +++ b/apps/sim/tools/hex/create_group.ts @@ -0,0 +1,78 @@ +import type { HexCreateGroupParams, HexCreateGroupResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const createGroupTool: ToolConfig = { + id: 'hex_create_group', + name: 'Hex Create Group', + description: 'Create a new group in the Hex workspace, optionally with initial members.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name for the new group', + }, + memberUserIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'JSON array of user UUIDs to add as initial group members (e.g., ["uuid1", "uuid2"])', + }, + }, + + request: { + url: 'https://app.hex.tech/api/v1/groups', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { name: params.name } + if (params.memberUserIds) { + let userIds: unknown + try { + userIds = + typeof params.memberUserIds === 'string' + ? JSON.parse(params.memberUserIds) + : params.memberUserIds + } catch { + throw new Error('memberUserIds must be a valid JSON array of user UUID strings') + } + if (!Array.isArray(userIds) || !userIds.every((id) => typeof id === 'string')) { + throw new Error('memberUserIds must be a valid JSON array of user UUID strings') + } + body.members = { users: userIds.map((id: string) => ({ id: id.trim() })) } + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + name: data.name ?? null, + createdAt: data.createdAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Newly created group UUID' }, + name: { type: 'string', description: 'Group name' }, + createdAt: { type: 'string', description: 'Creation timestamp' }, + }, +} diff --git a/apps/sim/tools/hex/deactivate_user.ts b/apps/sim/tools/hex/deactivate_user.ts new file mode 100644 index 0000000000..1f75120e75 --- /dev/null +++ b/apps/sim/tools/hex/deactivate_user.ts @@ -0,0 +1,60 @@ +import type { HexDeactivateUserParams, HexDeactivateUserResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const deactivateUserTool: ToolConfig = { + id: 'hex_deactivate_user', + name: 'Hex Deactivate User', + description: 'Deactivate a user in the Hex workspace.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + userId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the user to deactivate', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/users/${params.userId.trim()}/deactivate`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response, params) => { + if (response.status === 204 || response.ok) { + return { + success: true, + output: { + success: true, + userId: params?.userId ?? '', + }, + } + } + + const data = await response.json().catch(() => ({})) + return { + success: false, + output: { + success: false, + userId: params?.userId ?? '', + }, + error: (data as Record).message ?? 'Failed to deactivate user', + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the user was successfully deactivated' }, + userId: { type: 'string', description: 'User UUID that was deactivated' }, + }, +} diff --git a/apps/sim/tools/hex/delete_group.ts b/apps/sim/tools/hex/delete_group.ts new file mode 100644 index 0000000000..1135a6c87e --- /dev/null +++ b/apps/sim/tools/hex/delete_group.ts @@ -0,0 +1,60 @@ +import type { HexDeleteGroupParams, HexDeleteGroupResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const deleteGroupTool: ToolConfig = { + id: 'hex_delete_group', + name: 'Hex Delete Group', + description: 'Delete a group from the Hex workspace.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + groupId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the group to delete', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId.trim()}`, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response, params) => { + if (response.status === 204 || response.ok) { + return { + success: true, + output: { + success: true, + groupId: params?.groupId ?? '', + }, + } + } + + const data = await response.json().catch(() => ({})) + return { + success: false, + output: { + success: false, + groupId: params?.groupId ?? '', + }, + error: (data as Record).message ?? 'Failed to delete group', + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the group was successfully deleted' }, + groupId: { type: 'string', description: 'Group UUID that was deleted' }, + }, +} diff --git a/apps/sim/tools/hex/get_collection.ts b/apps/sim/tools/hex/get_collection.ts index 8222d88a92..8b5c0fe888 100644 --- a/apps/sim/tools/hex/get_collection.ts +++ b/apps/sim/tools/hex/get_collection.ts @@ -23,7 +23,7 @@ export const getCollectionTool: ToolConfig `https://app.hex.tech/api/v1/collections/${params.collectionId}`, + url: (params) => `https://app.hex.tech/api/v1/collections/${params.collectionId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_data_connection.ts b/apps/sim/tools/hex/get_data_connection.ts index 3b9e54b94f..68bd083838 100644 --- a/apps/sim/tools/hex/get_data_connection.ts +++ b/apps/sim/tools/hex/get_data_connection.ts @@ -27,7 +27,8 @@ export const getDataConnectionTool: ToolConfig< }, request: { - url: (params) => `https://app.hex.tech/api/v1/data-connections/${params.dataConnectionId}`, + url: (params) => + `https://app.hex.tech/api/v1/data-connections/${params.dataConnectionId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_group.ts b/apps/sim/tools/hex/get_group.ts index c649e657a8..683bde7e51 100644 --- a/apps/sim/tools/hex/get_group.ts +++ b/apps/sim/tools/hex/get_group.ts @@ -23,7 +23,7 @@ export const getGroupTool: ToolConfig = }, request: { - url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId}`, + url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_project.ts b/apps/sim/tools/hex/get_project.ts index fda718f2f6..fd5ecb29f3 100644 --- a/apps/sim/tools/hex/get_project.ts +++ b/apps/sim/tools/hex/get_project.ts @@ -24,7 +24,7 @@ export const getProjectTool: ToolConfig `https://app.hex.tech/api/v1/projects/${params.projectId}`, + url: (params) => `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_project_runs.ts b/apps/sim/tools/hex/get_project_runs.ts index 9d2897d900..0495603de2 100644 --- a/apps/sim/tools/hex/get_project_runs.ts +++ b/apps/sim/tools/hex/get_project_runs.ts @@ -40,6 +40,12 @@ export const getProjectRunsTool: ToolConfig ({ @@ -78,6 +85,8 @@ export const getProjectRunsTool: ToolConfig ({ diff --git a/apps/sim/tools/hex/get_run_status.ts b/apps/sim/tools/hex/get_run_status.ts index 90dd26cdb0..0c0de4a870 100644 --- a/apps/sim/tools/hex/get_run_status.ts +++ b/apps/sim/tools/hex/get_run_status.ts @@ -31,7 +31,7 @@ export const getRunStatusTool: ToolConfig - `https://app.hex.tech/api/v1/projects/${params.projectId}/runs/${params.runId}`, + `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}/runs/${params.runId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/index.ts b/apps/sim/tools/hex/index.ts index 9a561587d7..e96b0bb53f 100644 --- a/apps/sim/tools/hex/index.ts +++ b/apps/sim/tools/hex/index.ts @@ -1,5 +1,8 @@ import { cancelRunTool } from '@/tools/hex/cancel_run' import { createCollectionTool } from '@/tools/hex/create_collection' +import { createGroupTool } from '@/tools/hex/create_group' +import { deactivateUserTool } from '@/tools/hex/deactivate_user' +import { deleteGroupTool } from '@/tools/hex/delete_group' import { getCollectionTool } from '@/tools/hex/get_collection' import { getDataConnectionTool } from '@/tools/hex/get_data_connection' import { getGroupTool } from '@/tools/hex/get_group' @@ -13,10 +16,15 @@ import { listGroupsTool } from '@/tools/hex/list_groups' import { listProjectsTool } from '@/tools/hex/list_projects' import { listUsersTool } from '@/tools/hex/list_users' import { runProjectTool } from '@/tools/hex/run_project' +import { updateCollectionTool } from '@/tools/hex/update_collection' +import { updateGroupTool } from '@/tools/hex/update_group' import { updateProjectTool } from '@/tools/hex/update_project' export const hexCancelRunTool = cancelRunTool export const hexCreateCollectionTool = createCollectionTool +export const hexCreateGroupTool = createGroupTool +export const hexDeactivateUserTool = deactivateUserTool +export const hexDeleteGroupTool = deleteGroupTool export const hexGetCollectionTool = getCollectionTool export const hexGetDataConnectionTool = getDataConnectionTool export const hexGetGroupTool = getGroupTool @@ -30,4 +38,6 @@ export const hexListGroupsTool = listGroupsTool export const hexListProjectsTool = listProjectsTool export const hexListUsersTool = listUsersTool export const hexRunProjectTool = runProjectTool +export const hexUpdateCollectionTool = updateCollectionTool +export const hexUpdateGroupTool = updateGroupTool export const hexUpdateProjectTool = updateProjectTool diff --git a/apps/sim/tools/hex/list_collections.ts b/apps/sim/tools/hex/list_collections.ts index 9902db0d15..beaf4054f5 100644 --- a/apps/sim/tools/hex/list_collections.ts +++ b/apps/sim/tools/hex/list_collections.ts @@ -27,6 +27,18 @@ export const listCollectionsTool: ToolConfig typeof c === 'string')) { + throw new Error('categories must be a valid JSON array of category name strings') + } + for (const category of categories) { + searchParams.append('categories[]', category) + } + } + if (params.sortBy) searchParams.set('sortBy', params.sortBy) + if (params.sortDirection) searchParams.set('sortDirection', params.sortDirection) + if (params.after) searchParams.set('after', params.after) + if (params.before) searchParams.set('before', params.before) const qs = searchParams.toString() return `https://app.hex.tech/api/v1/projects${qs ? `?${qs}` : ''}` }, @@ -80,6 +166,8 @@ export const listProjectsTool: ToolConfig visibility: 'user-or-llm', description: 'Filter users by group UUID', }, + userIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of user UUIDs to filter by', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cursor to fetch the page of results after this value', + }, + before: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cursor to fetch the page of results before this value', + }, }, request: { @@ -47,6 +65,9 @@ export const listUsersTool: ToolConfig if (params.sortBy) searchParams.set('sortBy', params.sortBy) if (params.sortDirection) searchParams.set('sortDirection', params.sortDirection) if (params.groupId) searchParams.set('groupId', params.groupId) + if (params.userIds) searchParams.set('userIds', params.userIds) + if (params.after) searchParams.set('after', params.after) + if (params.before) searchParams.set('before', params.before) const qs = searchParams.toString() return `https://app.hex.tech/api/v1/users${qs ? `?${qs}` : ''}` }, @@ -69,8 +90,11 @@ export const listUsersTool: ToolConfig name: (u.name as string) ?? null, email: (u.email as string) ?? null, role: (u.role as string) ?? null, + lastLoginDate: (u.lastLoginDate as string) ?? null, })), total: users.length, + after: data.pagination?.after ?? null, + before: data.pagination?.before ?? null, }, } }, @@ -90,9 +114,20 @@ export const listUsersTool: ToolConfig description: 'User role (ADMIN, MANAGER, EDITOR, EXPLORER, MEMBER, GUEST, EMBEDDED_USER, ANONYMOUS)', }, + lastLoginDate: { + type: 'string', + description: 'Last login timestamp', + optional: true, + }, }, }, }, total: { type: 'number', description: 'Total number of users returned' }, + after: { type: 'string', description: 'Cursor for the next page of results', optional: true }, + before: { + type: 'string', + description: 'Cursor for the previous page of results', + optional: true, + }, }, } diff --git a/apps/sim/tools/hex/run_project.ts b/apps/sim/tools/hex/run_project.ts index 3d11ac0346..6d1a9ff602 100644 --- a/apps/sim/tools/hex/run_project.ts +++ b/apps/sim/tools/hex/run_project.ts @@ -52,10 +52,23 @@ export const runProjectTool: ToolConfig `https://app.hex.tech/api/v1/projects/${params.projectId}/runs`, + url: (params) => `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}/runs`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, @@ -65,10 +78,14 @@ export const runProjectTool: ToolConfig = {} if (params.inputParams) { - body.inputParams = - typeof params.inputParams === 'string' - ? JSON.parse(params.inputParams) - : params.inputParams + try { + body.inputParams = + typeof params.inputParams === 'string' + ? JSON.parse(params.inputParams) + : params.inputParams + } catch { + throw new Error('inputParams must be valid JSON') + } } if (params.dryRun !== undefined) body.dryRun = params.dryRun if (params.updateCache !== undefined) body.updateCache = params.updateCache @@ -76,6 +93,22 @@ export const runProjectTool: ToolConfig total: number + after: string | null + before: string | null } } @@ -159,6 +171,8 @@ export interface HexRunProjectParams { updateCache?: boolean updatePublishedResults?: boolean useCachedSqlResults?: boolean + viewId?: string + notifications?: string } export interface HexRunProjectResponse extends ToolResponse { @@ -212,6 +226,7 @@ export interface HexGetProjectRunsParams { limit?: number offset?: number statusFilter?: string + runTriggerFilter?: string } export interface HexGetProjectRunsResponse extends ToolResponse { @@ -229,6 +244,8 @@ export interface HexGetProjectRunsResponse extends ToolResponse { }> total: number traceId: string | null + nextPage: string | null + previousPage: string | null } } @@ -262,6 +279,9 @@ export interface HexListUsersParams { sortBy?: string sortDirection?: string groupId?: string + userIds?: string + after?: string + before?: string } export interface HexListUsersResponse extends ToolResponse { @@ -271,8 +291,11 @@ export interface HexListUsersResponse extends ToolResponse { name: string email: string role: string + lastLoginDate: string | null }> total: number + after: string | null + before: string | null } } @@ -280,6 +303,8 @@ export interface HexListCollectionsParams { apiKey: string limit?: number sortBy?: string + after?: string + before?: string } export interface HexListCollectionsResponse extends ToolResponse { @@ -291,6 +316,8 @@ export interface HexListCollectionsResponse extends ToolResponse { creator: { email: string; id: string } | null }> total: number + after: string | null + before: string | null } } @@ -299,6 +326,8 @@ export interface HexListDataConnectionsParams { limit?: number sortBy?: string sortDirection?: string + after?: string + before?: string } export interface HexListDataConnectionsResponse extends ToolResponse { @@ -313,6 +342,8 @@ export interface HexListDataConnectionsResponse extends ToolResponse { allowWritebackCells: boolean | null }> total: number + after: string | null + before: string | null } } @@ -338,6 +369,8 @@ export interface HexListGroupsParams { limit?: number sortBy?: string sortDirection?: string + after?: string + before?: string } export interface HexListGroupsResponse extends ToolResponse { @@ -348,6 +381,8 @@ export interface HexListGroupsResponse extends ToolResponse { createdAt: string | null }> total: number + after: string | null + before: string | null } } @@ -410,6 +445,76 @@ export interface HexCreateCollectionResponse extends ToolResponse { } } +export interface HexUpdateCollectionParams { + apiKey: string + collectionId: string + name?: string + description?: string +} + +export interface HexUpdateCollectionResponse extends ToolResponse { + output: { + id: string + name: string + description: string | null + creator: { email: string; id: string } | null + } +} + +export interface HexCreateGroupParams { + apiKey: string + name: string + memberUserIds?: string +} + +export interface HexCreateGroupResponse extends ToolResponse { + output: { + id: string + name: string + createdAt: string | null + } +} + +export interface HexUpdateGroupParams { + apiKey: string + groupId: string + name?: string + addUserIds?: string + removeUserIds?: string +} + +export interface HexUpdateGroupResponse extends ToolResponse { + output: { + id: string + name: string + createdAt: string | null + } +} + +export interface HexDeleteGroupParams { + apiKey: string + groupId: string +} + +export interface HexDeleteGroupResponse extends ToolResponse { + output: { + success: boolean + groupId: string + } +} + +export interface HexDeactivateUserParams { + apiKey: string + userId: string +} + +export interface HexDeactivateUserResponse extends ToolResponse { + output: { + success: boolean + userId: string + } +} + export type HexResponse = | HexListProjectsResponse | HexGetProjectResponse @@ -427,3 +532,8 @@ export type HexResponse = | HexGetDataConnectionResponse | HexGetCollectionResponse | HexCreateCollectionResponse + | HexUpdateCollectionResponse + | HexCreateGroupResponse + | HexUpdateGroupResponse + | HexDeleteGroupResponse + | HexDeactivateUserResponse diff --git a/apps/sim/tools/hex/update_collection.ts b/apps/sim/tools/hex/update_collection.ts new file mode 100644 index 0000000000..a21c2aee1d --- /dev/null +++ b/apps/sim/tools/hex/update_collection.ts @@ -0,0 +1,85 @@ +import type { HexUpdateCollectionParams, HexUpdateCollectionResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const updateCollectionTool: ToolConfig< + HexUpdateCollectionParams, + HexUpdateCollectionResponse +> = { + id: 'hex_update_collection', + name: 'Hex Update Collection', + description: 'Update the name or description of an existing Hex collection.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + collectionId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the collection to update', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name for the collection', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New description for the collection', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/collections/${params.collectionId.trim()}`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.name) body.name = params.name + if (params.description !== undefined) body.description = params.description + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + name: data.name ?? null, + description: data.description ?? null, + creator: data.creator + ? { email: data.creator.email ?? null, id: data.creator.id ?? null } + : null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Collection UUID' }, + name: { type: 'string', description: 'Collection name' }, + description: { type: 'string', description: 'Collection description', optional: true }, + creator: { + type: 'object', + description: 'Collection creator', + optional: true, + properties: { + email: { type: 'string', description: 'Creator email' }, + id: { type: 'string', description: 'Creator UUID' }, + }, + }, + }, +} diff --git a/apps/sim/tools/hex/update_group.ts b/apps/sim/tools/hex/update_group.ts new file mode 100644 index 0000000000..d02f48c5a1 --- /dev/null +++ b/apps/sim/tools/hex/update_group.ts @@ -0,0 +1,106 @@ +import type { HexUpdateGroupParams, HexUpdateGroupResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const updateGroupTool: ToolConfig = { + id: 'hex_update_group', + name: 'Hex Update Group', + description: 'Rename a Hex group or add/remove members from it.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + groupId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the group to update', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name for the group', + }, + addUserIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'JSON array of user UUIDs to add to the group (e.g., ["uuid1", "uuid2"])', + }, + removeUserIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'JSON array of user UUIDs to remove from the group (e.g., ["uuid1", "uuid2"])', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId.trim()}`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.name) body.name = params.name + + const parseIds = (value: unknown): string[] => { + let parsed: unknown + try { + parsed = typeof value === 'string' ? JSON.parse(value) : value + } catch { + throw new Error( + 'addUserIds/removeUserIds must be a valid JSON array of user UUID strings' + ) + } + if (!Array.isArray(parsed) || !parsed.every((id) => typeof id === 'string')) { + throw new Error( + 'addUserIds/removeUserIds must be a valid JSON array of user UUID strings' + ) + } + return parsed + } + + if (params.addUserIds || params.removeUserIds) { + const members: Record = {} + if (params.addUserIds) { + members.add = { users: parseIds(params.addUserIds).map((id) => ({ id: id.trim() })) } + } + if (params.removeUserIds) { + members.remove = { + users: parseIds(params.removeUserIds).map((id) => ({ id: id.trim() })), + } + } + body.members = members + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + name: data.name ?? null, + createdAt: data.createdAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Group UUID' }, + name: { type: 'string', description: 'Group name' }, + createdAt: { type: 'string', description: 'Creation timestamp' }, + }, +} diff --git a/apps/sim/tools/hex/update_project.ts b/apps/sim/tools/hex/update_project.ts index e8da0a3c27..faf75cfb9a 100644 --- a/apps/sim/tools/hex/update_project.ts +++ b/apps/sim/tools/hex/update_project.ts @@ -30,7 +30,7 @@ export const updateProjectTool: ToolConfig `https://app.hex.tech/api/v1/projects/${params.projectId}`, + url: (params) => `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 1154d6c593..b947a88a56 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1556,6 +1556,9 @@ import { guardrailsValidateTool } from '@/tools/guardrails' import { hexCancelRunTool, hexCreateCollectionTool, + hexCreateGroupTool, + hexDeactivateUserTool, + hexDeleteGroupTool, hexGetCollectionTool, hexGetDataConnectionTool, hexGetGroupTool, @@ -1569,6 +1572,8 @@ import { hexListProjectsTool, hexListUsersTool, hexRunProjectTool, + hexUpdateCollectionTool, + hexUpdateGroupTool, hexUpdateProjectTool, } from '@/tools/hex' import { httpRequestTool, webhookRequestTool } from '@/tools/http' @@ -4638,6 +4643,9 @@ export const tools: Record = { guardrails_validate: guardrailsValidateTool, hex_cancel_run: hexCancelRunTool, hex_create_collection: hexCreateCollectionTool, + hex_create_group: hexCreateGroupTool, + hex_deactivate_user: hexDeactivateUserTool, + hex_delete_group: hexDeleteGroupTool, hex_get_collection: hexGetCollectionTool, hex_get_data_connection: hexGetDataConnectionTool, hex_get_group: hexGetGroupTool, @@ -4651,6 +4659,8 @@ export const tools: Record = { hex_list_projects: hexListProjectsTool, hex_list_users: hexListUsersTool, hex_run_project: hexRunProjectTool, + hex_update_collection: hexUpdateCollectionTool, + hex_update_group: hexUpdateGroupTool, hex_update_project: hexUpdateProjectTool, instantly_activate_campaign: instantlyActivateCampaignTool, instantly_create_campaign: instantlyCreateCampaignTool,