Files
Flowise/packages/components/nodes/tools/GoogleSheets/core.ts
T
Henry Heng 30c4180d97 Feature/Add teams, gmail, outlook tools (#4577)
* add teams, gmail, outlook tools

* update docs link

* update credentials for oauth2

* add jira tool

* add google drive, google calendar, google sheets tools, powerpoint, excel, word doc loader

* update jira logo

* Refactor Gmail and Outlook tools to remove maxOutputLength parameter and enhance request handling. Update response formatting to include parameters in the output. Adjust Google Drive tools to simplify success messages by removing unnecessary parameter details.
2025-06-06 19:52:04 +01:00

632 lines
20 KiB
TypeScript

import { z } from 'zod'
import fetch from 'node-fetch'
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
import { TOOL_ARGS_PREFIX } from '../../../src/agents'
export const desc = `Use this when you want to access Google Sheets API for managing spreadsheets and values`
export interface Headers {
[key: string]: string
}
export interface Body {
[key: string]: any
}
export interface RequestParameters {
headers?: Headers
body?: Body
url?: string
description?: string
name?: string
actions?: string[]
accessToken?: string
defaultParams?: any
}
// Define schemas for different Google Sheets operations
// Spreadsheet Schemas
const CreateSpreadsheetSchema = z.object({
title: z.string().describe('The title of the spreadsheet'),
sheetCount: z.number().optional().default(1).describe('Number of sheets to create'),
locale: z.string().optional().describe('The locale of the spreadsheet (e.g., en_US)'),
timeZone: z.string().optional().describe('The time zone of the spreadsheet (e.g., America/New_York)')
})
const GetSpreadsheetSchema = z.object({
spreadsheetId: z.string().describe('The ID of the spreadsheet to retrieve'),
ranges: z.string().optional().describe('Comma-separated list of ranges to retrieve'),
includeGridData: z.boolean().optional().default(false).describe('True if grid data should be returned')
})
const UpdateSpreadsheetSchema = z.object({
spreadsheetId: z.string().describe('The ID of the spreadsheet to update'),
title: z.string().optional().describe('New title for the spreadsheet'),
locale: z.string().optional().describe('New locale for the spreadsheet'),
timeZone: z.string().optional().describe('New time zone for the spreadsheet')
})
// Values Schemas
const GetValuesSchema = z.object({
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
range: z.string().describe('The A1 notation of the range to retrieve values from'),
valueRenderOption: z
.enum(['FORMATTED_VALUE', 'UNFORMATTED_VALUE', 'FORMULA'])
.optional()
.default('FORMATTED_VALUE')
.describe('How values should be represented'),
dateTimeRenderOption: z
.enum(['SERIAL_NUMBER', 'FORMATTED_STRING'])
.optional()
.default('FORMATTED_STRING')
.describe('How dates should be represented'),
majorDimension: z.enum(['ROWS', 'COLUMNS']).optional().default('ROWS').describe('The major dimension that results should use')
})
const UpdateValuesSchema = z.object({
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
range: z.string().describe('The A1 notation of the range to update'),
values: z.string().describe('JSON array of values to write (e.g., [["A1", "B1"], ["A2", "B2"]])'),
valueInputOption: z.enum(['RAW', 'USER_ENTERED']).optional().default('USER_ENTERED').describe('How input data should be interpreted'),
majorDimension: z.enum(['ROWS', 'COLUMNS']).optional().default('ROWS').describe('The major dimension of the values')
})
const AppendValuesSchema = z.object({
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
range: z.string().describe('The A1 notation of the range to append to'),
values: z.string().describe('JSON array of values to append'),
valueInputOption: z.enum(['RAW', 'USER_ENTERED']).optional().default('USER_ENTERED').describe('How input data should be interpreted'),
insertDataOption: z.enum(['OVERWRITE', 'INSERT_ROWS']).optional().default('OVERWRITE').describe('How data should be inserted'),
majorDimension: z.enum(['ROWS', 'COLUMNS']).optional().default('ROWS').describe('The major dimension of the values')
})
const ClearValuesSchema = z.object({
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
range: z.string().describe('The A1 notation of the range to clear')
})
const BatchGetValuesSchema = z.object({
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
ranges: z.string().describe('Comma-separated list of ranges to retrieve'),
valueRenderOption: z
.enum(['FORMATTED_VALUE', 'UNFORMATTED_VALUE', 'FORMULA'])
.optional()
.default('FORMATTED_VALUE')
.describe('How values should be represented'),
dateTimeRenderOption: z
.enum(['SERIAL_NUMBER', 'FORMATTED_STRING'])
.optional()
.default('FORMATTED_STRING')
.describe('How dates should be represented'),
majorDimension: z.enum(['ROWS', 'COLUMNS']).optional().default('ROWS').describe('The major dimension that results should use')
})
const BatchUpdateValuesSchema = z.object({
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
valueInputOption: z.enum(['RAW', 'USER_ENTERED']).optional().default('USER_ENTERED').describe('How input data should be interpreted'),
values: z
.string()
.describe('JSON array of value ranges to update (e.g., [{"range": "A1:B2", "values": [["A1", "B1"], ["A2", "B2"]]}])'),
includeValuesInResponse: z.boolean().optional().default(false).describe('Whether to return the updated values in the response')
})
const BatchClearValuesSchema = z.object({
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
ranges: z.string().describe('Comma-separated list of ranges to clear')
})
class BaseGoogleSheetsTool extends DynamicStructuredTool {
protected accessToken: string = ''
constructor(args: any) {
super(args)
this.accessToken = args.accessToken ?? ''
}
async makeGoogleSheetsRequest({
endpoint,
method = 'GET',
body,
params
}: {
endpoint: string
method?: string
body?: any
params?: any
}): Promise<string> {
const url = `https://sheets.googleapis.com/v4/${endpoint}`
const headers = {
Authorization: `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
Accept: 'application/json',
...this.headers
}
const response = await fetch(url, {
method,
headers,
body: body ? JSON.stringify(body) : undefined
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Google Sheets API Error ${response.status}: ${response.statusText} - ${errorText}`)
}
const data = await response.text()
return data + TOOL_ARGS_PREFIX + JSON.stringify(params)
}
}
// Spreadsheet Tools
class CreateSpreadsheetTool extends BaseGoogleSheetsTool {
defaultParams: any
constructor(args: any) {
const toolInput = {
name: 'create_spreadsheet',
description: 'Create a new Google Spreadsheet',
schema: CreateSpreadsheetSchema,
baseUrl: '',
method: 'POST',
headers: {}
}
super({
...toolInput,
accessToken: args.accessToken
})
this.defaultParams = args.defaultParams || {}
}
async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
const body: any = {
properties: {
title: params.title
}
}
if (params.locale) body.properties.locale = params.locale
if (params.timeZone) body.properties.timeZone = params.timeZone
// Add sheets if specified
if (params.sheetCount && params.sheetCount > 1) {
body.sheets = []
for (let i = 0; i < params.sheetCount; i++) {
body.sheets.push({
properties: {
title: i === 0 ? 'Sheet1' : `Sheet${i + 1}`
}
})
}
}
return await this.makeGoogleSheetsRequest({
endpoint: 'spreadsheets',
method: 'POST',
body,
params
})
}
}
class GetSpreadsheetTool extends BaseGoogleSheetsTool {
defaultParams: any
constructor(args: any) {
const toolInput = {
name: 'get_spreadsheet',
description: 'Get a Google Spreadsheet by ID',
schema: GetSpreadsheetSchema,
baseUrl: '',
method: 'GET',
headers: {}
}
super({
...toolInput,
accessToken: args.accessToken
})
this.defaultParams = args.defaultParams || {}
}
async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
const queryParams = new URLSearchParams()
if (params.ranges) {
params.ranges.split(',').forEach((range: string) => {
queryParams.append('ranges', range.trim())
})
}
if (params.includeGridData) queryParams.append('includeGridData', 'true')
const queryString = queryParams.toString()
const endpoint = `spreadsheets/${params.spreadsheetId}${queryString ? `?${queryString}` : ''}`
return await this.makeGoogleSheetsRequest({
endpoint,
method: 'GET',
params
})
}
}
class UpdateSpreadsheetTool extends BaseGoogleSheetsTool {
defaultParams: any
constructor(args: any) {
const toolInput = {
name: 'update_spreadsheet',
description: 'Update a Google Spreadsheet properties',
schema: UpdateSpreadsheetSchema,
baseUrl: '',
method: 'POST',
headers: {}
}
super({
...toolInput,
accessToken: args.accessToken
})
this.defaultParams = args.defaultParams || {}
}
async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
const requests = []
if (params.title || params.locale || params.timeZone) {
const updateProperties: any = {}
if (params.title) updateProperties.title = params.title
if (params.locale) updateProperties.locale = params.locale
if (params.timeZone) updateProperties.timeZone = params.timeZone
requests.push({
updateSpreadsheetProperties: {
properties: updateProperties,
fields: Object.keys(updateProperties).join(',')
}
})
}
const body = { requests }
return await this.makeGoogleSheetsRequest({
endpoint: `spreadsheets/${params.spreadsheetId}:batchUpdate`,
method: 'POST',
body,
params
})
}
}
// Values Tools
class GetValuesTool extends BaseGoogleSheetsTool {
defaultParams: any
constructor(args: any) {
const toolInput = {
name: 'get_values',
description: 'Get values from a Google Spreadsheet range',
schema: GetValuesSchema,
baseUrl: '',
method: 'GET',
headers: {}
}
super({
...toolInput,
accessToken: args.accessToken
})
this.defaultParams = args.defaultParams || {}
}
async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
const queryParams = new URLSearchParams()
if (params.valueRenderOption) queryParams.append('valueRenderOption', params.valueRenderOption)
if (params.dateTimeRenderOption) queryParams.append('dateTimeRenderOption', params.dateTimeRenderOption)
if (params.majorDimension) queryParams.append('majorDimension', params.majorDimension)
const queryString = queryParams.toString()
const encodedRange = encodeURIComponent(params.range)
const endpoint = `spreadsheets/${params.spreadsheetId}/values/${encodedRange}${queryString ? `?${queryString}` : ''}`
return await this.makeGoogleSheetsRequest({
endpoint,
method: 'GET',
params
})
}
}
class UpdateValuesTool extends BaseGoogleSheetsTool {
defaultParams: any
constructor(args: any) {
const toolInput = {
name: 'update_values',
description: 'Update values in a Google Spreadsheet range',
schema: UpdateValuesSchema,
baseUrl: '',
method: 'PUT',
headers: {}
}
super({
...toolInput,
accessToken: args.accessToken
})
this.defaultParams = args.defaultParams || {}
}
async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
let values
try {
values = JSON.parse(params.values)
} catch (error) {
throw new Error('Values must be a valid JSON array')
}
const body = {
values,
majorDimension: params.majorDimension || 'ROWS'
}
const queryParams = new URLSearchParams()
queryParams.append('valueInputOption', params.valueInputOption || 'USER_ENTERED')
const encodedRange = encodeURIComponent(params.range)
const endpoint = `spreadsheets/${params.spreadsheetId}/values/${encodedRange}?${queryParams.toString()}`
return await this.makeGoogleSheetsRequest({
endpoint,
method: 'PUT',
body,
params
})
}
}
class AppendValuesTool extends BaseGoogleSheetsTool {
defaultParams: any
constructor(args: any) {
const toolInput = {
name: 'append_values',
description: 'Append values to a Google Spreadsheet range',
schema: AppendValuesSchema,
baseUrl: '',
method: 'POST',
headers: {}
}
super({
...toolInput,
accessToken: args.accessToken
})
this.defaultParams = args.defaultParams || {}
}
async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
let values
try {
values = JSON.parse(params.values)
} catch (error) {
throw new Error('Values must be a valid JSON array')
}
const body = {
values,
majorDimension: params.majorDimension || 'ROWS'
}
const queryParams = new URLSearchParams()
queryParams.append('valueInputOption', params.valueInputOption || 'USER_ENTERED')
queryParams.append('insertDataOption', params.insertDataOption || 'OVERWRITE')
const encodedRange = encodeURIComponent(params.range)
const endpoint = `spreadsheets/${params.spreadsheetId}/values/${encodedRange}:append?${queryParams.toString()}`
return await this.makeGoogleSheetsRequest({
endpoint,
method: 'POST',
body,
params
})
}
}
class ClearValuesTool extends BaseGoogleSheetsTool {
defaultParams: any
constructor(args: any) {
const toolInput = {
name: 'clear_values',
description: 'Clear values from a Google Spreadsheet range',
schema: ClearValuesSchema,
baseUrl: '',
method: 'POST',
headers: {}
}
super({
...toolInput,
accessToken: args.accessToken
})
this.defaultParams = args.defaultParams || {}
}
async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
const encodedRange = encodeURIComponent(params.range)
const endpoint = `spreadsheets/${params.spreadsheetId}/values/${encodedRange}:clear`
return await this.makeGoogleSheetsRequest({
endpoint,
method: 'POST',
body: {},
params
})
}
}
class BatchGetValuesTool extends BaseGoogleSheetsTool {
defaultParams: any
constructor(args: any) {
const toolInput = {
name: 'batch_get_values',
description: 'Get values from multiple Google Spreadsheet ranges',
schema: BatchGetValuesSchema,
baseUrl: '',
method: 'GET',
headers: {}
}
super({
...toolInput,
accessToken: args.accessToken
})
this.defaultParams = args.defaultParams || {}
}
async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
const queryParams = new URLSearchParams()
// Add ranges
params.ranges.split(',').forEach((range: string) => {
queryParams.append('ranges', range.trim())
})
if (params.valueRenderOption) queryParams.append('valueRenderOption', params.valueRenderOption)
if (params.dateTimeRenderOption) queryParams.append('dateTimeRenderOption', params.dateTimeRenderOption)
if (params.majorDimension) queryParams.append('majorDimension', params.majorDimension)
const endpoint = `spreadsheets/${params.spreadsheetId}/values:batchGet?${queryParams.toString()}`
return await this.makeGoogleSheetsRequest({
endpoint,
method: 'GET',
params
})
}
}
class BatchUpdateValuesTool extends BaseGoogleSheetsTool {
defaultParams: any
constructor(args: any) {
const toolInput = {
name: 'batch_update_values',
description: 'Update values in multiple Google Spreadsheet ranges',
schema: BatchUpdateValuesSchema,
baseUrl: '',
method: 'POST',
headers: {}
}
super({
...toolInput,
accessToken: args.accessToken
})
this.defaultParams = args.defaultParams || {}
}
async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
let valueRanges
try {
valueRanges = JSON.parse(params.values)
} catch (error) {
throw new Error('Values must be a valid JSON array of value ranges')
}
const body = {
valueInputOption: params.valueInputOption || 'USER_ENTERED',
data: valueRanges,
includeValuesInResponse: params.includeValuesInResponse || false
}
const endpoint = `spreadsheets/${params.spreadsheetId}/values:batchUpdate`
return await this.makeGoogleSheetsRequest({
endpoint,
method: 'POST',
body,
params
})
}
}
class BatchClearValuesTool extends BaseGoogleSheetsTool {
defaultParams: any
constructor(args: any) {
const toolInput = {
name: 'batch_clear_values',
description: 'Clear values from multiple Google Spreadsheet ranges',
schema: BatchClearValuesSchema,
baseUrl: '',
method: 'POST',
headers: {}
}
super({
...toolInput,
accessToken: args.accessToken
})
this.defaultParams = args.defaultParams || {}
}
async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
const ranges = params.ranges.split(',').map((range: string) => range.trim())
const body = { ranges }
const endpoint = `spreadsheets/${params.spreadsheetId}/values:batchClear`
return await this.makeGoogleSheetsRequest({
endpoint,
method: 'POST',
body,
params
})
}
}
export const createGoogleSheetsTools = (args?: RequestParameters): DynamicStructuredTool[] => {
const { actions = [], accessToken, defaultParams } = args || {}
const tools: DynamicStructuredTool[] = []
// Define all available tools
const toolClasses = {
// Spreadsheet tools
createSpreadsheet: CreateSpreadsheetTool,
getSpreadsheet: GetSpreadsheetTool,
updateSpreadsheet: UpdateSpreadsheetTool,
// Values tools
getValues: GetValuesTool,
updateValues: UpdateValuesTool,
appendValues: AppendValuesTool,
clearValues: ClearValuesTool,
batchGetValues: BatchGetValuesTool,
batchUpdateValues: BatchUpdateValuesTool,
batchClearValues: BatchClearValuesTool
}
// Create tools based on requested actions
actions.forEach((action) => {
const ToolClass = toolClasses[action as keyof typeof toolClasses]
if (ToolClass) {
tools.push(new ToolClass({ accessToken, defaultParams }))
}
})
return tools
}