diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index d87043ba6a..b3136684b1 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -17,6 +17,7 @@ "google_calendar", "google_docs", "google_drive", + "google_forms", "google_search", "google_sheets", "huggingface", diff --git a/apps/docs/content/docs/en/tools/sharepoint.mdx b/apps/docs/content/docs/en/tools/sharepoint.mdx index b32b84a326..712e135a8a 100644 --- a/apps/docs/content/docs/en/tools/sharepoint.mdx +++ b/apps/docs/content/docs/en/tools/sharepoint.mdx @@ -183,6 +183,25 @@ Update the properties (fields) on a SharePoint list item | --------- | ---- | ----------- | | `item` | object | Updated SharePoint list item | +### `sharepoint_add_list_items` + +Add a new item to a SharePoint list + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `siteSelector` | string | No | Select the SharePoint site | +| `siteId` | string | No | The ID of the SharePoint site \(internal use\) | +| `listId` | string | Yes | The ID of the list to add the item to | +| `listItemFields` | object | Yes | Field values for the new list item | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `item` | object | Created SharePoint list item | + ## Notes diff --git a/apps/sim/blocks/blocks/sharepoint.ts b/apps/sim/blocks/blocks/sharepoint.ts index 176446bb62..e7878cc117 100644 --- a/apps/sim/blocks/blocks/sharepoint.ts +++ b/apps/sim/blocks/blocks/sharepoint.ts @@ -16,7 +16,6 @@ export const SharepointBlock: BlockConfig = { bgColor: '#E0E0E0', icon: MicrosoftSharepointIcon, subBlocks: [ - // Operation selector { id: 'operation', title: 'Operation', @@ -29,9 +28,9 @@ export const SharepointBlock: BlockConfig = { { label: 'Create List', id: 'create_list' }, { label: 'Read List', id: 'read_list' }, { label: 'Update List', id: 'update_list' }, + { label: 'Add List Items', id: 'add_list_items' }, ], }, - // Sharepoint Credentials { id: 'credential', title: 'Microsoft Account', @@ -81,6 +80,7 @@ export const SharepointBlock: BlockConfig = { 'create_list', 'read_list', 'update_list', + 'add_list_items', ], }, }, @@ -111,7 +111,7 @@ export const SharepointBlock: BlockConfig = { layout: 'full', placeholder: 'Enter list ID (GUID). Required for Update; optional for Read.', canonicalParamId: 'listId', - condition: { field: 'operation', value: ['read_list', 'update_list'] }, + condition: { field: 'operation', value: ['read_list', 'update_list', 'add_list_items'] }, }, { @@ -178,7 +178,7 @@ export const SharepointBlock: BlockConfig = { layout: 'full', placeholder: 'Enter list item fields', canonicalParamId: 'listItemFields', - condition: { field: 'operation', value: 'update_list' }, + condition: { field: 'operation', value: ['update_list', 'add_list_items'] }, }, ], tools: { @@ -189,6 +189,7 @@ export const SharepointBlock: BlockConfig = { 'sharepoint_create_list', 'sharepoint_get_list', 'sharepoint_update_list', + 'sharepoint_add_list_items', ], config: { tool: (params) => { @@ -205,6 +206,8 @@ export const SharepointBlock: BlockConfig = { return 'sharepoint_get_list' case 'update_list': return 'sharepoint_update_list' + case 'add_list_items': + return 'sharepoint_add_list_items' default: throw new Error(`Invalid Sharepoint operation: ${params.operation}`) } @@ -212,7 +215,6 @@ export const SharepointBlock: BlockConfig = { params: (params) => { const { credential, siteSelector, manualSiteId, mimeType, ...rest } = params - // Use siteSelector if provided, otherwise use manualSiteId const effectiveSiteId = (siteSelector || manualSiteId || '').trim() const { @@ -234,12 +236,10 @@ export const SharepointBlock: BlockConfig = { }) } } - // Ensure listItemFields is an object for the tool schema if (typeof parsedItemFields !== 'object' || parsedItemFields === null) { parsedItemFields = undefined } - // Sanitize item ID (required by tool) const rawItemId = providedItemId ?? listItemId const sanitizedItemId = rawItemId === undefined || rawItemId === null @@ -252,10 +252,9 @@ export const SharepointBlock: BlockConfig = { return undefined } - // Debug logging for update_list param mapping - if (others.operation === 'update_list') { + if (others.operation === 'update_list' || others.operation === 'add_list_items') { try { - logger.info('SharepointBlock update_list param check', { + logger.info('SharepointBlock list item param check', { siteId: effectiveSiteId || undefined, listId: (others as any)?.listId, listTitle: (others as any)?.listTitle, @@ -275,7 +274,6 @@ export const SharepointBlock: BlockConfig = { pageSize: others.pageSize ? Number.parseInt(others.pageSize as string, 10) : undefined, mimeType: mimeType, ...others, - // Map to tool param names itemId: sanitizedItemId, listItemFields: parsedItemFields, includeColumns: coerceBoolean(includeColumns), @@ -287,26 +285,20 @@ export const SharepointBlock: BlockConfig = { inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Microsoft account credential' }, - // Create Page operation inputs pageName: { type: 'string', description: 'Page name' }, pageContent: { type: 'string', description: 'Page content' }, pageTitle: { type: 'string', description: 'Page title' }, - // Read Page operation inputs pageId: { type: 'string', description: 'Page ID' }, - // List operation inputs siteSelector: { type: 'string', description: 'Site selector' }, manualSiteId: { type: 'string', description: 'Manual site ID' }, pageSize: { type: 'number', description: 'Results per page' }, - // Create List operation inputs listDisplayName: { type: 'string', description: 'List display name' }, listDescription: { type: 'string', description: 'List description' }, listTemplate: { type: 'string', description: 'List template' }, - // Read List operation inputs listId: { type: 'string', description: 'List ID' }, listTitle: { type: 'string', description: 'List title' }, includeColumns: { type: 'boolean', description: 'Include columns in response' }, includeItems: { type: 'boolean', description: 'Include items in response' }, - // Update List Item operation inputs listItemId: { type: 'string', description: 'List item ID' }, listItemFields: { type: 'string', description: 'List item fields' }, }, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index a682c678cd..baca3f5c0c 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -145,6 +145,7 @@ import { redditGetCommentsTool, redditGetPostsTool, redditHotPostsTool } from '@ import { s3GetObjectTool } from '@/tools/s3' import { searchTool as serperSearch } from '@/tools/serper' import { + sharepointAddListItemTool, sharepointCreateListTool, sharepointCreatePageTool, sharepointGetListTool, @@ -370,6 +371,7 @@ export const tools: Record = { sharepoint_get_list: sharepointGetListTool, sharepoint_create_list: sharepointCreateListTool, sharepoint_update_list: sharepointUpdateListItemTool, + sharepoint_add_list_items: sharepointAddListItemTool, // Provider chat tools // Provider chat tools - handled separately in agent blocks } diff --git a/apps/sim/tools/sharepoint/add_list_items.ts b/apps/sim/tools/sharepoint/add_list_items.ts new file mode 100644 index 0000000000..5e295a99fc --- /dev/null +++ b/apps/sim/tools/sharepoint/add_list_items.ts @@ -0,0 +1,167 @@ +import { createLogger } from '@/lib/logs/console/logger' +import type { SharepointAddListItemResponse, SharepointToolParams } from '@/tools/sharepoint/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('SharePointAddListItem') + +export const addListItemTool: ToolConfig = { + id: 'sharepoint_add_list_items', + name: 'Add SharePoint List Item', + description: 'Add a new item to a SharePoint list', + version: '1.0', + + oauth: { + required: true, + provider: 'sharepoint', + additionalScopes: ['openid', 'profile', 'email', 'Sites.ReadWrite.All', 'offline_access'], + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the SharePoint API', + }, + siteSelector: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Select the SharePoint site', + }, + siteId: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'The ID of the SharePoint site (internal use)', + }, + listId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'The ID of the list to add the item to', + }, + listItemFields: { + type: 'object', + required: true, + visibility: 'user-only', + description: 'Field values for the new list item', + }, + }, + + request: { + url: (params) => { + const siteId = params.siteId || params.siteSelector || 'root' + if (!params.listId) { + throw new Error('listId must be provided') + } + const listSegment = params.listId + return `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listSegment}/items` + }, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + if (!params.listItemFields || Object.keys(params.listItemFields).length === 0) { + throw new Error('listItemFields must not be empty') + } + + const providedFields = + typeof params.listItemFields === 'object' && + params.listItemFields !== null && + 'fields' in (params.listItemFields as Record) && + Object.keys(params.listItemFields as Record).length === 1 + ? ((params.listItemFields as any).fields as Record) + : (params.listItemFields as Record) + + if (!providedFields || Object.keys(providedFields).length === 0) { + throw new Error('No fields provided to create the SharePoint list item') + } + + const readOnlyFields = new Set([ + 'Id', + 'id', + 'UniqueId', + 'GUID', + 'ContentTypeId', + 'Created', + 'Modified', + 'Author', + 'Editor', + 'CreatedBy', + 'ModifiedBy', + 'AuthorId', + 'EditorId', + '_UIVersionString', + 'Attachments', + 'FileRef', + 'FileDirRef', + 'FileLeafRef', + ]) + + const entries = Object.entries(providedFields) + const creatableEntries = entries.filter(([key]) => !readOnlyFields.has(key)) + + if (creatableEntries.length !== entries.length) { + const removed = entries.filter(([key]) => readOnlyFields.has(key)).map(([key]) => key) + logger.warn('Removed read-only SharePoint fields from create', { + removed, + }) + } + + if (creatableEntries.length === 0) { + const requestedKeys = Object.keys(providedFields) + throw new Error( + `All provided fields are read-only and cannot be set: ${requestedKeys.join(', ')}` + ) + } + + const sanitizedFields = Object.fromEntries(creatableEntries) + + logger.info('Creating SharePoint list item', { + listId: params.listId, + fieldsKeys: Object.keys(sanitizedFields), + }) + + return { + fields: sanitizedFields, + } + }, + }, + + transformResponse: async (response: Response, params) => { + let data: any + try { + data = await response.json() + } catch { + data = undefined + } + + const itemId: string | undefined = data?.id + const fields: Record | undefined = data?.fields || params?.listItemFields + + return { + success: true, + output: { + item: { + id: itemId || 'unknown', + fields, + }, + }, + } + }, + + outputs: { + item: { + type: 'object', + description: 'Created SharePoint list item', + properties: { + id: { type: 'string', description: 'Item ID' }, + fields: { type: 'object', description: 'Field values for the new item' }, + }, + }, + }, +} diff --git a/apps/sim/tools/sharepoint/index.ts b/apps/sim/tools/sharepoint/index.ts index fbfcd80a3f..1961156f1a 100644 --- a/apps/sim/tools/sharepoint/index.ts +++ b/apps/sim/tools/sharepoint/index.ts @@ -1,3 +1,4 @@ +import { addListItemTool } from '@/tools/sharepoint/add_list_items' import { createListTool } from '@/tools/sharepoint/create_list' import { createPageTool } from '@/tools/sharepoint/create_page' import { getListTool } from '@/tools/sharepoint/get_list' @@ -11,3 +12,4 @@ export const sharepointGetListTool = getListTool export const sharepointListSitesTool = listSitesTool export const sharepointReadPageTool = readPageTool export const sharepointUpdateListItemTool = updateListItemTool +export const sharepointAddListItemTool = addListItemTool diff --git a/apps/sim/tools/sharepoint/types.ts b/apps/sim/tools/sharepoint/types.ts index 4ce4847c05..afa5899779 100644 --- a/apps/sim/tools/sharepoint/types.ts +++ b/apps/sim/tools/sharepoint/types.ts @@ -259,6 +259,7 @@ export type SharepointResponse = | SharepointGetListResponse | SharepointCreateListResponse | SharepointUpdateListItemResponse + | SharepointAddListItemResponse export interface SharepointGetListResponse extends ToolResponse { output: { @@ -282,3 +283,12 @@ export interface SharepointUpdateListItemResponse extends ToolResponse { } } } + +export interface SharepointAddListItemResponse extends ToolResponse { + output: { + item: { + id: string + fields?: Record + } + } +}