Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/content/docs/en/tools/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"google_calendar",
"google_docs",
"google_drive",
"google_forms",
"google_search",
"google_sheets",
"huggingface",
Expand Down
19 changes: 19 additions & 0 deletions apps/docs/content/docs/en/tools/sharepoint.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 9 additions & 17 deletions apps/sim/blocks/blocks/sharepoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
bgColor: '#E0E0E0',
icon: MicrosoftSharepointIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
Expand All @@ -29,9 +28,9 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
{ 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',
Expand Down Expand Up @@ -81,6 +80,7 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
'create_list',
'read_list',
'update_list',
'add_list_items',
],
},
},
Expand Down Expand Up @@ -111,7 +111,7 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
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'] },
},

{
Expand Down Expand Up @@ -178,7 +178,7 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
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: {
Expand All @@ -189,6 +189,7 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
'sharepoint_create_list',
'sharepoint_get_list',
'sharepoint_update_list',
'sharepoint_add_list_items',
],
config: {
tool: (params) => {
Expand All @@ -205,14 +206,15 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
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}`)
}
},
params: (params) => {
const { credential, siteSelector, manualSiteId, mimeType, ...rest } = params

// Use siteSelector if provided, otherwise use manualSiteId
const effectiveSiteId = (siteSelector || manualSiteId || '').trim()

const {
Expand All @@ -234,12 +236,10 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
})
}
}
// 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
Expand All @@ -252,10 +252,9 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
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,
Expand All @@ -275,7 +274,6 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
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),
Expand All @@ -287,26 +285,20 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
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' },
},
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/tools/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -370,6 +371,7 @@ export const tools: Record<string, ToolConfig> = {
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
}
167 changes: 167 additions & 0 deletions apps/sim/tools/sharepoint/add_list_items.ts
Original file line number Diff line number Diff line change
@@ -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<SharepointToolParams, SharepointAddListItemResponse> = {
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<string, unknown>) &&
Object.keys(params.listItemFields as Record<string, unknown>).length === 1
? ((params.listItemFields as any).fields as Record<string, unknown>)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Type assertion to any should be avoided. Consider defining a proper type for the fields structure.

Context Used: Context - Avoid using type assertions to 'any' in TypeScript. Instead, ensure proper type definitions are used to maintain type safety. (link)

: (params.listItemFields as Record<string, unknown>)

if (!providedFields || Object.keys(providedFields).length === 0) {
throw new Error('No fields provided to create the SharePoint list item')
}

const readOnlyFields = new Set<string>([
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did you get these?

'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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Type assertion to any should be avoided. Define a proper interface for the Graph API response.

Context Used: Context - Avoid using type assertions to 'any' in TypeScript. Instead, ensure proper type definitions are used to maintain type safety. (link)

try {
data = await response.json()
} catch {
data = undefined
}

const itemId: string | undefined = data?.id
const fields: Record<string, unknown> | 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' },
},
},
},
}
2 changes: 2 additions & 0 deletions apps/sim/tools/sharepoint/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -11,3 +12,4 @@ export const sharepointGetListTool = getListTool
export const sharepointListSitesTool = listSitesTool
export const sharepointReadPageTool = readPageTool
export const sharepointUpdateListItemTool = updateListItemTool
export const sharepointAddListItemTool = addListItemTool
10 changes: 10 additions & 0 deletions apps/sim/tools/sharepoint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ export type SharepointResponse =
| SharepointGetListResponse
| SharepointCreateListResponse
| SharepointUpdateListItemResponse
| SharepointAddListItemResponse

export interface SharepointGetListResponse extends ToolResponse {
output: {
Expand All @@ -282,3 +283,12 @@ export interface SharepointUpdateListItemResponse extends ToolResponse {
}
}
}

export interface SharepointAddListItemResponse extends ToolResponse {
output: {
item: {
id: string
fields?: Record<string, unknown>
}
}
}