mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
improvement(tools): added visibility for tools that were missing it, added new google and github tools (#2874)
* improvement(tools): added visibility for tools that were missing it, added new google tools * fixed the name for google forms * revert schema enrichers change * fixed block ordering
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import type {
|
||||
GoogleSheetsAppendResponse,
|
||||
GoogleSheetsToolParams,
|
||||
GoogleSheetsV2AppendResponse,
|
||||
GoogleSheetsV2ToolParams,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -226,3 +228,197 @@ export const appendTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsAppendRe
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const appendV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2AppendResponse> = {
|
||||
id: 'google_sheets_append_v2',
|
||||
name: 'Append to Google Sheets V2',
|
||||
description: 'Append data to the end of a specific sheet in a Google Sheets spreadsheet',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet to append to',
|
||||
},
|
||||
sheetName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the sheet/tab to append to',
|
||||
},
|
||||
values: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The data to append as a 2D array (e.g. [["Alice", 30], ["Bob", 25]]) or array of objects.',
|
||||
},
|
||||
valueInputOption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The format of the data to append',
|
||||
},
|
||||
insertDataOption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'How to insert the data (OVERWRITE or INSERT_ROWS)',
|
||||
},
|
||||
includeValuesInResponse: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Whether to include the appended values in the response',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const sheetName = params.sheetName?.trim()
|
||||
if (!sheetName) {
|
||||
throw new Error('Sheet name is required')
|
||||
}
|
||||
|
||||
const url = new URL(
|
||||
`https://sheets.googleapis.com/v4/spreadsheets/${params.spreadsheetId}/values/${encodeURIComponent(sheetName)}:append`
|
||||
)
|
||||
|
||||
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
|
||||
url.searchParams.append('valueInputOption', valueInputOption)
|
||||
|
||||
if (params.insertDataOption) {
|
||||
url.searchParams.append('insertDataOption', params.insertDataOption)
|
||||
}
|
||||
|
||||
if (params.includeValuesInResponse) {
|
||||
url.searchParams.append('includeValuesInResponse', 'true')
|
||||
}
|
||||
|
||||
return url.toString()
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let processedValues: any = params.values || []
|
||||
|
||||
if (typeof processedValues === 'string') {
|
||||
try {
|
||||
processedValues = JSON.parse(processedValues)
|
||||
} catch (_error) {
|
||||
try {
|
||||
const sanitizedInput = (processedValues as string)
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\t/g, '\\t')
|
||||
processedValues = JSON.parse(sanitizedInput)
|
||||
} catch (_secondError) {
|
||||
processedValues = [[processedValues]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(processedValues) &&
|
||||
processedValues.length > 0 &&
|
||||
typeof processedValues[0] === 'object' &&
|
||||
!Array.isArray(processedValues[0])
|
||||
) {
|
||||
const allKeys = new Set<string>()
|
||||
processedValues.forEach((obj: any) => {
|
||||
if (obj && typeof obj === 'object') {
|
||||
Object.keys(obj).forEach((key) => allKeys.add(key))
|
||||
}
|
||||
})
|
||||
const headers = Array.from(allKeys)
|
||||
|
||||
const rows = processedValues.map((obj: any) => {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return Array(headers.length).fill('')
|
||||
}
|
||||
return headers.map((key) => {
|
||||
const value = obj[key]
|
||||
if (value !== null && typeof value === 'object') {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
return value === undefined ? '' : value
|
||||
})
|
||||
})
|
||||
|
||||
processedValues = [headers, ...rows]
|
||||
} else if (!Array.isArray(processedValues)) {
|
||||
processedValues = [[String(processedValues)]]
|
||||
} else if (!processedValues.every((item: any) => Array.isArray(item))) {
|
||||
processedValues = (processedValues as any[]).map((row: any) =>
|
||||
Array.isArray(row) ? row : [String(row)]
|
||||
)
|
||||
}
|
||||
|
||||
const body: Record<string, any> = {
|
||||
majorDimension: params.majorDimension || 'ROWS',
|
||||
values: processedValues,
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
|
||||
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
|
||||
|
||||
const metadata = {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tableRange: data.tableRange ?? '',
|
||||
updatedRange: data.updates?.updatedRange ?? '',
|
||||
updatedRows: data.updates?.updatedRows ?? 0,
|
||||
updatedColumns: data.updates?.updatedColumns ?? 0,
|
||||
updatedCells: data.updates?.updatedCells ?? 0,
|
||||
metadata: {
|
||||
spreadsheetId: metadata.spreadsheetId,
|
||||
spreadsheetUrl: metadata.spreadsheetUrl,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tableRange: { type: 'string', description: 'Range of the table where data was appended' },
|
||||
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
|
||||
updatedRows: { type: 'number', description: 'Number of rows updated' },
|
||||
updatedColumns: { type: 'number', description: 'Number of columns updated' },
|
||||
updatedCells: { type: 'number', description: 'Number of cells updated' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
|
||||
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
import type {
|
||||
GoogleSheetsV2AppendResponse,
|
||||
GoogleSheetsV2ToolParams,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const appendV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2AppendResponse> = {
|
||||
id: 'google_sheets_append_v2',
|
||||
name: 'Append to Google Sheets V2',
|
||||
description: 'Append data to the end of a specific sheet in a Google Sheets spreadsheet',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet to append to',
|
||||
},
|
||||
sheetName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the sheet/tab to append to',
|
||||
},
|
||||
values: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The data to append as a 2D array (e.g. [["Alice", 30], ["Bob", 25]]) or array of objects.',
|
||||
},
|
||||
valueInputOption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The format of the data to append',
|
||||
},
|
||||
insertDataOption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'How to insert the data (OVERWRITE or INSERT_ROWS)',
|
||||
},
|
||||
includeValuesInResponse: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Whether to include the appended values in the response',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const sheetName = params.sheetName?.trim()
|
||||
if (!sheetName) {
|
||||
throw new Error('Sheet name is required')
|
||||
}
|
||||
|
||||
const url = new URL(
|
||||
`https://sheets.googleapis.com/v4/spreadsheets/${params.spreadsheetId}/values/${encodeURIComponent(sheetName)}:append`
|
||||
)
|
||||
|
||||
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
|
||||
url.searchParams.append('valueInputOption', valueInputOption)
|
||||
|
||||
if (params.insertDataOption) {
|
||||
url.searchParams.append('insertDataOption', params.insertDataOption)
|
||||
}
|
||||
|
||||
if (params.includeValuesInResponse) {
|
||||
url.searchParams.append('includeValuesInResponse', 'true')
|
||||
}
|
||||
|
||||
return url.toString()
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let processedValues: any = params.values || []
|
||||
|
||||
if (typeof processedValues === 'string') {
|
||||
try {
|
||||
processedValues = JSON.parse(processedValues)
|
||||
} catch (_error) {
|
||||
try {
|
||||
const sanitizedInput = (processedValues as string)
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\t/g, '\\t')
|
||||
processedValues = JSON.parse(sanitizedInput)
|
||||
} catch (_secondError) {
|
||||
processedValues = [[processedValues]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(processedValues) &&
|
||||
processedValues.length > 0 &&
|
||||
typeof processedValues[0] === 'object' &&
|
||||
!Array.isArray(processedValues[0])
|
||||
) {
|
||||
const allKeys = new Set<string>()
|
||||
processedValues.forEach((obj: any) => {
|
||||
if (obj && typeof obj === 'object') {
|
||||
Object.keys(obj).forEach((key) => allKeys.add(key))
|
||||
}
|
||||
})
|
||||
const headers = Array.from(allKeys)
|
||||
|
||||
const rows = processedValues.map((obj: any) => {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return Array(headers.length).fill('')
|
||||
}
|
||||
return headers.map((key) => {
|
||||
const value = obj[key]
|
||||
if (value !== null && typeof value === 'object') {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
return value === undefined ? '' : value
|
||||
})
|
||||
})
|
||||
|
||||
processedValues = [headers, ...rows]
|
||||
} else if (!Array.isArray(processedValues)) {
|
||||
processedValues = [[String(processedValues)]]
|
||||
} else if (!processedValues.every((item: any) => Array.isArray(item))) {
|
||||
processedValues = (processedValues as any[]).map((row: any) =>
|
||||
Array.isArray(row) ? row : [String(row)]
|
||||
)
|
||||
}
|
||||
|
||||
const body: Record<string, any> = {
|
||||
majorDimension: params.majorDimension || 'ROWS',
|
||||
values: processedValues,
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
|
||||
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
|
||||
|
||||
const metadata = {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tableRange: data.tableRange ?? '',
|
||||
updatedRange: data.updates?.updatedRange ?? '',
|
||||
updatedRows: data.updates?.updatedRows ?? 0,
|
||||
updatedColumns: data.updates?.updatedColumns ?? 0,
|
||||
updatedCells: data.updates?.updatedCells ?? 0,
|
||||
metadata: {
|
||||
spreadsheetId: metadata.spreadsheetId,
|
||||
spreadsheetUrl: metadata.spreadsheetUrl,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tableRange: { type: 'string', description: 'Range of the table where data was appended' },
|
||||
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
|
||||
updatedRows: { type: 'number', description: 'Number of rows updated' },
|
||||
updatedColumns: { type: 'number', description: 'Number of columns updated' },
|
||||
updatedCells: { type: 'number', description: 'Number of cells updated' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
|
||||
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
112
apps/sim/tools/google_sheets/batch_clear.ts
Normal file
112
apps/sim/tools/google_sheets/batch_clear.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type {
|
||||
GoogleSheetsV2BatchClearParams,
|
||||
GoogleSheetsV2BatchClearResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const batchClearV2Tool: ToolConfig<
|
||||
GoogleSheetsV2BatchClearParams,
|
||||
GoogleSheetsV2BatchClearResponse
|
||||
> = {
|
||||
id: 'google_sheets_batch_clear_v2',
|
||||
name: 'Batch Clear Google Sheets V2',
|
||||
description: 'Clear multiple ranges in a Google Sheets spreadsheet in a single request',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet',
|
||||
},
|
||||
ranges: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of ranges to clear (e.g., ["Sheet1!A1:D10", "Sheet2!A1:B5"]). Each range should include sheet name.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const spreadsheetId = params.spreadsheetId?.trim()
|
||||
if (!spreadsheetId) {
|
||||
throw new Error('Spreadsheet ID is required')
|
||||
}
|
||||
|
||||
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values:batchClear`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const ranges = params.ranges
|
||||
if (!ranges || !Array.isArray(ranges) || ranges.length === 0) {
|
||||
throw new Error('At least one range is required')
|
||||
}
|
||||
|
||||
return {
|
||||
ranges,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const spreadsheetId = data.spreadsheetId ?? ''
|
||||
const clearedRanges = data.clearedRanges ?? []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
spreadsheetId,
|
||||
clearedRanges,
|
||||
metadata: {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
spreadsheetId: { type: 'string', description: 'The spreadsheet ID' },
|
||||
clearedRanges: {
|
||||
type: 'array',
|
||||
description: 'Array of ranges that were cleared',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
|
||||
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
143
apps/sim/tools/google_sheets/batch_get.ts
Normal file
143
apps/sim/tools/google_sheets/batch_get.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import type {
|
||||
GoogleSheetsV2BatchGetParams,
|
||||
GoogleSheetsV2BatchGetResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const batchGetV2Tool: ToolConfig<
|
||||
GoogleSheetsV2BatchGetParams,
|
||||
GoogleSheetsV2BatchGetResponse
|
||||
> = {
|
||||
id: 'google_sheets_batch_get_v2',
|
||||
name: 'Batch Read Google Sheets V2',
|
||||
description: 'Read multiple ranges from a Google Sheets spreadsheet in a single request',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet',
|
||||
},
|
||||
ranges: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of ranges to read (e.g., ["Sheet1!A1:D10", "Sheet2!A1:B5"]). Each range should include sheet name.',
|
||||
},
|
||||
majorDimension: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The major dimension of values: "ROWS" (default) or "COLUMNS"',
|
||||
},
|
||||
valueRenderOption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'How values should be rendered: "FORMATTED_VALUE" (default), "UNFORMATTED_VALUE", or "FORMULA"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const spreadsheetId = params.spreadsheetId?.trim()
|
||||
if (!spreadsheetId) {
|
||||
throw new Error('Spreadsheet ID is required')
|
||||
}
|
||||
|
||||
const ranges = params.ranges
|
||||
if (!ranges || !Array.isArray(ranges) || ranges.length === 0) {
|
||||
throw new Error('At least one range is required')
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
ranges.forEach((range: string) => {
|
||||
queryParams.append('ranges', range)
|
||||
})
|
||||
|
||||
if (params.majorDimension) {
|
||||
queryParams.append('majorDimension', params.majorDimension)
|
||||
}
|
||||
|
||||
if (params.valueRenderOption) {
|
||||
queryParams.append('valueRenderOption', params.valueRenderOption)
|
||||
}
|
||||
|
||||
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values:batchGet?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const valueRanges =
|
||||
data.valueRanges?.map((vr: any) => ({
|
||||
range: vr.range ?? '',
|
||||
majorDimension: vr.majorDimension ?? 'ROWS',
|
||||
values: vr.values ?? [],
|
||||
})) ?? []
|
||||
|
||||
const spreadsheetId = data.spreadsheetId ?? ''
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
spreadsheetId,
|
||||
valueRanges,
|
||||
metadata: {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
spreadsheetId: { type: 'string', description: 'The spreadsheet ID' },
|
||||
valueRanges: {
|
||||
type: 'array',
|
||||
description: 'Array of value ranges read from the spreadsheet',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
range: { type: 'string', description: 'The range that was read' },
|
||||
majorDimension: { type: 'string', description: 'Major dimension (ROWS or COLUMNS)' },
|
||||
values: { type: 'array', description: 'The cell values as a 2D array' },
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
|
||||
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
149
apps/sim/tools/google_sheets/batch_update.ts
Normal file
149
apps/sim/tools/google_sheets/batch_update.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import type {
|
||||
GoogleSheetsV2BatchUpdateParams,
|
||||
GoogleSheetsV2BatchUpdateResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const batchUpdateV2Tool: ToolConfig<
|
||||
GoogleSheetsV2BatchUpdateParams,
|
||||
GoogleSheetsV2BatchUpdateResponse
|
||||
> = {
|
||||
id: 'google_sheets_batch_update_v2',
|
||||
name: 'Batch Update Google Sheets V2',
|
||||
description: 'Update multiple ranges in a Google Sheets spreadsheet in a single request',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet',
|
||||
},
|
||||
data: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of value ranges to update. Each item should have "range" (e.g., "Sheet1!A1:D10") and "values" (2D array).',
|
||||
},
|
||||
valueInputOption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'How input data should be interpreted: "RAW" or "USER_ENTERED" (default). USER_ENTERED parses formulas.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const spreadsheetId = params.spreadsheetId?.trim()
|
||||
if (!spreadsheetId) {
|
||||
throw new Error('Spreadsheet ID is required')
|
||||
}
|
||||
|
||||
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values:batchUpdate`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const data = params.data
|
||||
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||
throw new Error('At least one data range is required')
|
||||
}
|
||||
|
||||
return {
|
||||
valueInputOption: params.valueInputOption ?? 'USER_ENTERED',
|
||||
data: data.map((item: any) => ({
|
||||
range: item.range,
|
||||
values: item.values,
|
||||
})),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const responses =
|
||||
data.responses?.map((r: any) => ({
|
||||
spreadsheetId: r.spreadsheetId ?? '',
|
||||
updatedRange: r.updatedRange ?? '',
|
||||
updatedRows: r.updatedRows ?? 0,
|
||||
updatedColumns: r.updatedColumns ?? 0,
|
||||
updatedCells: r.updatedCells ?? 0,
|
||||
})) ?? []
|
||||
|
||||
const spreadsheetId = data.spreadsheetId ?? ''
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
spreadsheetId,
|
||||
totalUpdatedRows: data.totalUpdatedRows ?? 0,
|
||||
totalUpdatedColumns: data.totalUpdatedColumns ?? 0,
|
||||
totalUpdatedCells: data.totalUpdatedCells ?? 0,
|
||||
totalUpdatedSheets: data.totalUpdatedSheets ?? 0,
|
||||
responses,
|
||||
metadata: {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
spreadsheetId: { type: 'string', description: 'The spreadsheet ID' },
|
||||
totalUpdatedRows: { type: 'number', description: 'Total number of rows updated' },
|
||||
totalUpdatedColumns: { type: 'number', description: 'Total number of columns updated' },
|
||||
totalUpdatedCells: { type: 'number', description: 'Total number of cells updated' },
|
||||
totalUpdatedSheets: { type: 'number', description: 'Total number of sheets updated' },
|
||||
responses: {
|
||||
type: 'array',
|
||||
description: 'Array of update responses for each range',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'The spreadsheet ID' },
|
||||
updatedRange: { type: 'string', description: 'The range that was updated' },
|
||||
updatedRows: { type: 'number', description: 'Number of rows updated in this range' },
|
||||
updatedColumns: {
|
||||
type: 'number',
|
||||
description: 'Number of columns updated in this range',
|
||||
},
|
||||
updatedCells: { type: 'number', description: 'Number of cells updated in this range' },
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
|
||||
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import type {
|
||||
GoogleSheetsV2ReadResponse,
|
||||
GoogleSheetsV2ToolParams,
|
||||
GoogleSheetsV2ClearParams,
|
||||
GoogleSheetsV2ClearResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const readV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2ReadResponse> = {
|
||||
id: 'google_sheets_read_v2',
|
||||
name: 'Read from Google Sheets V2',
|
||||
description: 'Read data from a specific sheet in a Google Sheets spreadsheet',
|
||||
export const clearV2Tool: ToolConfig<GoogleSheetsV2ClearParams, GoogleSheetsV2ClearResponse> = {
|
||||
id: 'google_sheets_clear_v2',
|
||||
name: 'Clear Google Sheets Range V2',
|
||||
description: 'Clear values from a specific range in a Google Sheets spreadsheet',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
@@ -32,14 +32,13 @@ export const readV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2Read
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the sheet/tab to read from',
|
||||
description: 'The name of the sheet/tab to clear',
|
||||
},
|
||||
cellRange: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The cell range to read (e.g. "A1:D10"). Defaults to "A1:Z1000" if not specified.',
|
||||
description: 'The cell range to clear (e.g. "A1:D10"). Clears entire sheet if not specified.',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -55,12 +54,12 @@ export const readV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2Read
|
||||
throw new Error('Sheet name is required')
|
||||
}
|
||||
|
||||
const cellRange = params.cellRange?.trim() || 'A1:Z1000'
|
||||
const fullRange = `${sheetName}!${cellRange}`
|
||||
const cellRange = params.cellRange?.trim()
|
||||
const fullRange = cellRange ? `${sheetName}!${cellRange}` : sheetName
|
||||
|
||||
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(fullRange)}`
|
||||
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(fullRange)}:clear`
|
||||
},
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
@@ -68,16 +67,16 @@ export const readV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2Read
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: () => ({}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: GoogleSheetsV2ToolParams) => {
|
||||
transformResponse: async (response: Response, params?: GoogleSheetsV2ClearParams) => {
|
||||
const data = await response.json()
|
||||
|
||||
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
|
||||
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
|
||||
|
||||
const spreadsheetId = params?.spreadsheetId ?? ''
|
||||
const metadata = {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
@@ -86,9 +85,8 @@ export const readV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2Read
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
clearedRange: data.clearedRange ?? '',
|
||||
sheetName: params?.sheetName ?? '',
|
||||
range: data.range ?? '',
|
||||
values: data.values ?? [],
|
||||
metadata: {
|
||||
spreadsheetId: metadata.spreadsheetId,
|
||||
spreadsheetUrl: metadata.spreadsheetUrl,
|
||||
@@ -98,9 +96,8 @@ export const readV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2Read
|
||||
},
|
||||
|
||||
outputs: {
|
||||
sheetName: { type: 'string', description: 'Name of the sheet that was read' },
|
||||
range: { type: 'string', description: 'The range of cells that was read' },
|
||||
values: { type: 'array', description: 'The cell values as a 2D array' },
|
||||
clearedRange: { type: 'string', description: 'The range that was cleared' },
|
||||
sheetName: { type: 'string', description: 'Name of the sheet that was cleared' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
119
apps/sim/tools/google_sheets/copy_sheet.ts
Normal file
119
apps/sim/tools/google_sheets/copy_sheet.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import type {
|
||||
GoogleSheetsV2CopySheetParams,
|
||||
GoogleSheetsV2CopySheetResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const copySheetV2Tool: ToolConfig<
|
||||
GoogleSheetsV2CopySheetParams,
|
||||
GoogleSheetsV2CopySheetResponse
|
||||
> = {
|
||||
id: 'google_sheets_copy_sheet_v2',
|
||||
name: 'Copy Sheet V2',
|
||||
description: 'Copy a sheet from one spreadsheet to another',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
sourceSpreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the source spreadsheet',
|
||||
},
|
||||
sheetId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The ID of the sheet to copy (numeric ID, not the sheet name). Use Get Spreadsheet to find sheet IDs.',
|
||||
},
|
||||
destinationSpreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the destination spreadsheet where the sheet will be copied',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const sourceSpreadsheetId = params.sourceSpreadsheetId?.trim()
|
||||
if (!sourceSpreadsheetId) {
|
||||
throw new Error('Source spreadsheet ID is required')
|
||||
}
|
||||
|
||||
const sheetId = params.sheetId
|
||||
if (sheetId === undefined || sheetId === null) {
|
||||
throw new Error('Sheet ID is required')
|
||||
}
|
||||
|
||||
return `https://sheets.googleapis.com/v4/spreadsheets/${sourceSpreadsheetId}/sheets/${sheetId}:copyTo`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const destinationSpreadsheetId = params.destinationSpreadsheetId?.trim()
|
||||
if (!destinationSpreadsheetId) {
|
||||
throw new Error('Destination spreadsheet ID is required')
|
||||
}
|
||||
|
||||
return {
|
||||
destinationSpreadsheetId,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: GoogleSheetsV2CopySheetParams) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
sheetId: data.sheetId ?? 0,
|
||||
title: data.title ?? '',
|
||||
index: data.index ?? 0,
|
||||
sheetType: data.sheetType ?? 'GRID',
|
||||
destinationSpreadsheetId: params?.destinationSpreadsheetId ?? '',
|
||||
destinationSpreadsheetUrl: `https://docs.google.com/spreadsheets/d/${params?.destinationSpreadsheetId ?? ''}`,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
sheetId: {
|
||||
type: 'number',
|
||||
description: 'The ID of the newly created sheet in the destination',
|
||||
},
|
||||
title: { type: 'string', description: 'The title of the copied sheet' },
|
||||
index: { type: 'number', description: 'The index (position) of the copied sheet' },
|
||||
sheetType: { type: 'string', description: 'The type of the sheet (GRID, CHART, etc.)' },
|
||||
destinationSpreadsheetId: {
|
||||
type: 'string',
|
||||
description: 'The ID of the destination spreadsheet',
|
||||
},
|
||||
destinationSpreadsheetUrl: {
|
||||
type: 'string',
|
||||
description: 'URL to the destination spreadsheet',
|
||||
},
|
||||
},
|
||||
}
|
||||
139
apps/sim/tools/google_sheets/create_spreadsheet.ts
Normal file
139
apps/sim/tools/google_sheets/create_spreadsheet.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import type {
|
||||
GoogleSheetsV2CreateSpreadsheetParams,
|
||||
GoogleSheetsV2CreateSpreadsheetResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createSpreadsheetV2Tool: ToolConfig<
|
||||
GoogleSheetsV2CreateSpreadsheetParams,
|
||||
GoogleSheetsV2CreateSpreadsheetResponse
|
||||
> = {
|
||||
id: 'google_sheets_create_spreadsheet_v2',
|
||||
name: 'Create Spreadsheet V2',
|
||||
description: 'Create a new Google Sheets spreadsheet',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The title of the new spreadsheet',
|
||||
},
|
||||
sheetTitles: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of sheet names to create (e.g., ["Sheet1", "Data", "Summary"]). Defaults to a single "Sheet1".',
|
||||
},
|
||||
locale: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The locale of the spreadsheet (e.g., "en_US")',
|
||||
},
|
||||
timeZone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The time zone of the spreadsheet (e.g., "America/New_York")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://sheets.googleapis.com/v4/spreadsheets',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const title = params.title?.trim()
|
||||
if (!title) {
|
||||
throw new Error('Spreadsheet title is required')
|
||||
}
|
||||
|
||||
const sheetTitles = params.sheetTitles ?? ['Sheet1']
|
||||
const sheets = sheetTitles.map((sheetTitle: string, index: number) => ({
|
||||
properties: {
|
||||
title: sheetTitle,
|
||||
index,
|
||||
},
|
||||
}))
|
||||
|
||||
const body: any = {
|
||||
properties: {
|
||||
title,
|
||||
},
|
||||
sheets,
|
||||
}
|
||||
|
||||
if (params.locale) {
|
||||
body.properties.locale = params.locale
|
||||
}
|
||||
|
||||
if (params.timeZone) {
|
||||
body.properties.timeZone = params.timeZone
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const sheets =
|
||||
data.sheets?.map((sheet: any) => ({
|
||||
sheetId: sheet.properties?.sheetId ?? 0,
|
||||
title: sheet.properties?.title ?? '',
|
||||
index: sheet.properties?.index ?? 0,
|
||||
})) ?? []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
spreadsheetId: data.spreadsheetId ?? '',
|
||||
title: data.properties?.title ?? '',
|
||||
spreadsheetUrl: data.spreadsheetUrl ?? '',
|
||||
sheets,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
spreadsheetId: { type: 'string', description: 'The ID of the created spreadsheet' },
|
||||
title: { type: 'string', description: 'The title of the created spreadsheet' },
|
||||
spreadsheetUrl: { type: 'string', description: 'URL to the created spreadsheet' },
|
||||
sheets: {
|
||||
type: 'array',
|
||||
description: 'List of sheets created in the spreadsheet',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sheetId: { type: 'number', description: 'The sheet ID' },
|
||||
title: { type: 'string', description: 'The sheet title/name' },
|
||||
index: { type: 'number', description: 'The sheet index (position)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
112
apps/sim/tools/google_sheets/get_spreadsheet.ts
Normal file
112
apps/sim/tools/google_sheets/get_spreadsheet.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type {
|
||||
GoogleSheetsV2GetSpreadsheetParams,
|
||||
GoogleSheetsV2GetSpreadsheetResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getSpreadsheetV2Tool: ToolConfig<
|
||||
GoogleSheetsV2GetSpreadsheetParams,
|
||||
GoogleSheetsV2GetSpreadsheetResponse
|
||||
> = {
|
||||
id: 'google_sheets_get_spreadsheet_v2',
|
||||
name: 'Get Spreadsheet Info V2',
|
||||
description: 'Get metadata about a Google Sheets spreadsheet including title and sheet list',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet',
|
||||
},
|
||||
includeGridData: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to include grid data (cell values). Defaults to false.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const spreadsheetId = params.spreadsheetId?.trim()
|
||||
if (!spreadsheetId) {
|
||||
throw new Error('Spreadsheet ID is required')
|
||||
}
|
||||
|
||||
const includeGridData = params.includeGridData ? 'true' : 'false'
|
||||
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}?includeGridData=${includeGridData}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const sheets =
|
||||
data.sheets?.map((sheet: any) => ({
|
||||
sheetId: sheet.properties?.sheetId ?? 0,
|
||||
title: sheet.properties?.title ?? '',
|
||||
index: sheet.properties?.index ?? 0,
|
||||
rowCount: sheet.properties?.gridProperties?.rowCount ?? null,
|
||||
columnCount: sheet.properties?.gridProperties?.columnCount ?? null,
|
||||
hidden: sheet.properties?.hidden ?? false,
|
||||
})) ?? []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
spreadsheetId: data.spreadsheetId ?? '',
|
||||
title: data.properties?.title ?? '',
|
||||
locale: data.properties?.locale ?? null,
|
||||
timeZone: data.properties?.timeZone ?? null,
|
||||
spreadsheetUrl: data.spreadsheetUrl ?? '',
|
||||
sheets,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
spreadsheetId: { type: 'string', description: 'The spreadsheet ID' },
|
||||
title: { type: 'string', description: 'The title of the spreadsheet' },
|
||||
locale: { type: 'string', description: 'The locale of the spreadsheet', optional: true },
|
||||
timeZone: { type: 'string', description: 'The time zone of the spreadsheet', optional: true },
|
||||
spreadsheetUrl: { type: 'string', description: 'URL to the spreadsheet' },
|
||||
sheets: {
|
||||
type: 'array',
|
||||
description: 'List of sheets in the spreadsheet',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sheetId: { type: 'number', description: 'The sheet ID' },
|
||||
title: { type: 'string', description: 'The sheet title/name' },
|
||||
index: { type: 'number', description: 'The sheet index (position)' },
|
||||
rowCount: { type: 'number', description: 'Number of rows in the sheet' },
|
||||
columnCount: { type: 'number', description: 'Number of columns in the sheet' },
|
||||
hidden: { type: 'boolean', description: 'Whether the sheet is hidden' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import { appendTool } from '@/tools/google_sheets/append'
|
||||
import { appendV2Tool } from '@/tools/google_sheets/append_v2'
|
||||
import { readTool } from '@/tools/google_sheets/read'
|
||||
import { readV2Tool } from '@/tools/google_sheets/read_v2'
|
||||
import { updateTool } from '@/tools/google_sheets/update'
|
||||
import { updateV2Tool } from '@/tools/google_sheets/update_v2'
|
||||
import { writeTool } from '@/tools/google_sheets/write'
|
||||
import { writeV2Tool } from '@/tools/google_sheets/write_v2'
|
||||
import { appendTool, appendV2Tool } from '@/tools/google_sheets/append'
|
||||
import { batchClearV2Tool } from '@/tools/google_sheets/batch_clear'
|
||||
import { batchGetV2Tool } from '@/tools/google_sheets/batch_get'
|
||||
import { batchUpdateV2Tool } from '@/tools/google_sheets/batch_update'
|
||||
import { clearV2Tool } from '@/tools/google_sheets/clear'
|
||||
import { copySheetV2Tool } from '@/tools/google_sheets/copy_sheet'
|
||||
import { createSpreadsheetV2Tool } from '@/tools/google_sheets/create_spreadsheet'
|
||||
import { getSpreadsheetV2Tool } from '@/tools/google_sheets/get_spreadsheet'
|
||||
import { readTool, readV2Tool } from '@/tools/google_sheets/read'
|
||||
import { updateTool, updateV2Tool } from '@/tools/google_sheets/update'
|
||||
import { writeTool, writeV2Tool } from '@/tools/google_sheets/write'
|
||||
|
||||
// V1 exports
|
||||
export const googleSheetsReadTool = readTool
|
||||
@@ -18,3 +21,10 @@ export const googleSheetsReadV2Tool = readV2Tool
|
||||
export const googleSheetsWriteV2Tool = writeV2Tool
|
||||
export const googleSheetsUpdateV2Tool = updateV2Tool
|
||||
export const googleSheetsAppendV2Tool = appendV2Tool
|
||||
export const googleSheetsClearV2Tool = clearV2Tool
|
||||
export const googleSheetsGetSpreadsheetV2Tool = getSpreadsheetV2Tool
|
||||
export const googleSheetsCreateSpreadsheetV2Tool = createSpreadsheetV2Tool
|
||||
export const googleSheetsBatchGetV2Tool = batchGetV2Tool
|
||||
export const googleSheetsBatchUpdateV2Tool = batchUpdateV2Tool
|
||||
export const googleSheetsBatchClearV2Tool = batchClearV2Tool
|
||||
export const googleSheetsCopySheetV2Tool = copySheetV2Tool
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { GoogleSheetsReadResponse, GoogleSheetsToolParams } from '@/tools/google_sheets/types'
|
||||
import type {
|
||||
GoogleSheetsReadResponse,
|
||||
GoogleSheetsToolParams,
|
||||
GoogleSheetsV2ReadResponse,
|
||||
GoogleSheetsV2ToolParams,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const readTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsReadResponse> = {
|
||||
@@ -111,3 +116,111 @@ export const readTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsReadRespon
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const readV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2ReadResponse> = {
|
||||
id: 'google_sheets_read_v2',
|
||||
name: 'Read from Google Sheets V2',
|
||||
description: 'Read data from a specific sheet in a Google Sheets spreadsheet',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet',
|
||||
},
|
||||
sheetName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the sheet/tab to read from',
|
||||
},
|
||||
cellRange: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The cell range to read (e.g. "A1:D10"). Defaults to "A1:Z1000" if not specified.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const spreadsheetId = params.spreadsheetId?.trim()
|
||||
if (!spreadsheetId) {
|
||||
throw new Error('Spreadsheet ID is required')
|
||||
}
|
||||
|
||||
const sheetName = params.sheetName?.trim()
|
||||
if (!sheetName) {
|
||||
throw new Error('Sheet name is required')
|
||||
}
|
||||
|
||||
const cellRange = params.cellRange?.trim() || 'A1:Z1000'
|
||||
const fullRange = `${sheetName}!${cellRange}`
|
||||
|
||||
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(fullRange)}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: GoogleSheetsV2ToolParams) => {
|
||||
const data = await response.json()
|
||||
|
||||
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
|
||||
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
|
||||
|
||||
const metadata = {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
sheetName: params?.sheetName ?? '',
|
||||
range: data.range ?? '',
|
||||
values: data.values ?? [],
|
||||
metadata: {
|
||||
spreadsheetId: metadata.spreadsheetId,
|
||||
spreadsheetUrl: metadata.spreadsheetUrl,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
sheetName: { type: 'string', description: 'Name of the sheet that was read' },
|
||||
range: { type: 'string', description: 'The range of cells that was read' },
|
||||
values: { type: 'array', description: 'The cell values as a 2D array' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
|
||||
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -136,3 +136,157 @@ export type GoogleSheetsV2Response =
|
||||
| GoogleSheetsV2WriteResponse
|
||||
| GoogleSheetsV2UpdateResponse
|
||||
| GoogleSheetsV2AppendResponse
|
||||
| GoogleSheetsV2ClearResponse
|
||||
| GoogleSheetsV2GetSpreadsheetResponse
|
||||
| GoogleSheetsV2CreateSpreadsheetResponse
|
||||
| GoogleSheetsV2BatchGetResponse
|
||||
| GoogleSheetsV2BatchUpdateResponse
|
||||
| GoogleSheetsV2BatchClearResponse
|
||||
| GoogleSheetsV2CopySheetResponse
|
||||
|
||||
// V2 Clear Types
|
||||
export interface GoogleSheetsV2ClearParams {
|
||||
accessToken: string
|
||||
spreadsheetId: string
|
||||
sheetName: string
|
||||
cellRange?: string
|
||||
}
|
||||
|
||||
export interface GoogleSheetsV2ClearResponse extends ToolResponse {
|
||||
output: {
|
||||
clearedRange: string
|
||||
sheetName: string
|
||||
metadata: GoogleSheetsMetadata
|
||||
}
|
||||
}
|
||||
|
||||
// V2 Get Spreadsheet Types
|
||||
export interface GoogleSheetsV2GetSpreadsheetParams {
|
||||
accessToken: string
|
||||
spreadsheetId: string
|
||||
includeGridData?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleSheetsV2GetSpreadsheetResponse extends ToolResponse {
|
||||
output: {
|
||||
spreadsheetId: string
|
||||
title: string
|
||||
locale: string | null
|
||||
timeZone: string | null
|
||||
spreadsheetUrl: string
|
||||
sheets: {
|
||||
sheetId: number
|
||||
title: string
|
||||
index: number
|
||||
rowCount: number | null
|
||||
columnCount: number | null
|
||||
hidden: boolean
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
// V2 Create Spreadsheet Types
|
||||
export interface GoogleSheetsV2CreateSpreadsheetParams {
|
||||
accessToken: string
|
||||
title: string
|
||||
sheetTitles?: string[]
|
||||
locale?: string
|
||||
timeZone?: string
|
||||
}
|
||||
|
||||
export interface GoogleSheetsV2CreateSpreadsheetResponse extends ToolResponse {
|
||||
output: {
|
||||
spreadsheetId: string
|
||||
title: string
|
||||
spreadsheetUrl: string
|
||||
sheets: {
|
||||
sheetId: number
|
||||
title: string
|
||||
index: number
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
// V2 Batch Get Types
|
||||
export interface GoogleSheetsV2BatchGetParams {
|
||||
accessToken: string
|
||||
spreadsheetId: string
|
||||
ranges: string[]
|
||||
majorDimension?: 'ROWS' | 'COLUMNS'
|
||||
valueRenderOption?: 'FORMATTED_VALUE' | 'UNFORMATTED_VALUE' | 'FORMULA'
|
||||
}
|
||||
|
||||
export interface GoogleSheetsV2BatchGetResponse extends ToolResponse {
|
||||
output: {
|
||||
spreadsheetId: string
|
||||
valueRanges: {
|
||||
range: string
|
||||
majorDimension: string
|
||||
values: any[][]
|
||||
}[]
|
||||
metadata: GoogleSheetsMetadata
|
||||
}
|
||||
}
|
||||
|
||||
// V2 Batch Update Types
|
||||
export interface GoogleSheetsV2BatchUpdateParams {
|
||||
accessToken: string
|
||||
spreadsheetId: string
|
||||
data: {
|
||||
range: string
|
||||
values: any[][]
|
||||
}[]
|
||||
valueInputOption?: 'RAW' | 'USER_ENTERED'
|
||||
}
|
||||
|
||||
export interface GoogleSheetsV2BatchUpdateResponse extends ToolResponse {
|
||||
output: {
|
||||
spreadsheetId: string
|
||||
totalUpdatedRows: number
|
||||
totalUpdatedColumns: number
|
||||
totalUpdatedCells: number
|
||||
totalUpdatedSheets: number
|
||||
responses: {
|
||||
spreadsheetId: string
|
||||
updatedRange: string
|
||||
updatedRows: number
|
||||
updatedColumns: number
|
||||
updatedCells: number
|
||||
}[]
|
||||
metadata: GoogleSheetsMetadata
|
||||
}
|
||||
}
|
||||
|
||||
// V2 Batch Clear Types
|
||||
export interface GoogleSheetsV2BatchClearParams {
|
||||
accessToken: string
|
||||
spreadsheetId: string
|
||||
ranges: string[]
|
||||
}
|
||||
|
||||
export interface GoogleSheetsV2BatchClearResponse extends ToolResponse {
|
||||
output: {
|
||||
spreadsheetId: string
|
||||
clearedRanges: string[]
|
||||
metadata: GoogleSheetsMetadata
|
||||
}
|
||||
}
|
||||
|
||||
// V2 Copy Sheet Types
|
||||
export interface GoogleSheetsV2CopySheetParams {
|
||||
accessToken: string
|
||||
sourceSpreadsheetId: string
|
||||
sheetId: number
|
||||
destinationSpreadsheetId: string
|
||||
}
|
||||
|
||||
export interface GoogleSheetsV2CopySheetResponse extends ToolResponse {
|
||||
output: {
|
||||
sheetId: number
|
||||
title: string
|
||||
index: number
|
||||
sheetType: string
|
||||
destinationSpreadsheetId: string
|
||||
destinationSpreadsheetUrl: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type {
|
||||
GoogleSheetsToolParams,
|
||||
GoogleSheetsUpdateResponse,
|
||||
GoogleSheetsV2ToolParams,
|
||||
GoogleSheetsV2UpdateResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -179,3 +181,183 @@ export const updateTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsUpdateRe
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const updateV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2UpdateResponse> = {
|
||||
id: 'google_sheets_update_v2',
|
||||
name: 'Update Google Sheets V2',
|
||||
description: 'Update data in a specific sheet in a Google Sheets spreadsheet',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet to update',
|
||||
},
|
||||
sheetName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the sheet/tab to update',
|
||||
},
|
||||
cellRange: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The cell range to update (e.g. "A1:D10", "A1"). Defaults to "A1" if not specified.',
|
||||
},
|
||||
values: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The data to update as a 2D array (e.g. [["Name", "Age"], ["Alice", 30]]) or array of objects.',
|
||||
},
|
||||
valueInputOption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The format of the data to update',
|
||||
},
|
||||
includeValuesInResponse: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Whether to include the updated values in the response',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const sheetName = params.sheetName?.trim()
|
||||
if (!sheetName) {
|
||||
throw new Error('Sheet name is required')
|
||||
}
|
||||
|
||||
const cellRange = params.cellRange?.trim() || 'A1'
|
||||
const fullRange = `${sheetName}!${cellRange}`
|
||||
|
||||
const url = new URL(
|
||||
`https://sheets.googleapis.com/v4/spreadsheets/${params.spreadsheetId}/values/${encodeURIComponent(fullRange)}`
|
||||
)
|
||||
|
||||
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
|
||||
url.searchParams.append('valueInputOption', valueInputOption)
|
||||
|
||||
if (params.includeValuesInResponse) {
|
||||
url.searchParams.append('includeValuesInResponse', 'true')
|
||||
}
|
||||
|
||||
return url.toString()
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let processedValues: any = params.values || []
|
||||
|
||||
// Minimal shape enforcement: Google requires a 2D array
|
||||
if (!Array.isArray(processedValues)) {
|
||||
processedValues = [[processedValues]]
|
||||
} else if (!processedValues.every((item: any) => Array.isArray(item))) {
|
||||
processedValues = (processedValues as any[]).map((row: any) =>
|
||||
Array.isArray(row) ? row : [row]
|
||||
)
|
||||
}
|
||||
|
||||
// Handle array of objects
|
||||
if (
|
||||
Array.isArray(processedValues) &&
|
||||
processedValues.length > 0 &&
|
||||
typeof processedValues[0] === 'object' &&
|
||||
!Array.isArray(processedValues[0])
|
||||
) {
|
||||
const allKeys = new Set<string>()
|
||||
processedValues.forEach((obj: any) => {
|
||||
if (obj && typeof obj === 'object') {
|
||||
Object.keys(obj).forEach((key) => allKeys.add(key))
|
||||
}
|
||||
})
|
||||
const headers = Array.from(allKeys)
|
||||
|
||||
const rows = processedValues.map((obj: any) => {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return Array(headers.length).fill('')
|
||||
}
|
||||
return headers.map((key) => {
|
||||
const value = obj[key]
|
||||
if (value !== null && typeof value === 'object') {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
return value === undefined ? '' : value
|
||||
})
|
||||
})
|
||||
|
||||
processedValues = [headers, ...rows]
|
||||
}
|
||||
|
||||
const body: Record<string, any> = {
|
||||
majorDimension: params.majorDimension || 'ROWS',
|
||||
values: processedValues,
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
|
||||
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
|
||||
|
||||
const metadata = {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
updatedRange: data.updatedRange ?? null,
|
||||
updatedRows: data.updatedRows ?? 0,
|
||||
updatedColumns: data.updatedColumns ?? 0,
|
||||
updatedCells: data.updatedCells ?? 0,
|
||||
metadata: {
|
||||
spreadsheetId: metadata.spreadsheetId,
|
||||
spreadsheetUrl: metadata.spreadsheetUrl,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
|
||||
updatedRows: { type: 'number', description: 'Number of rows updated' },
|
||||
updatedColumns: { type: 'number', description: 'Number of columns updated' },
|
||||
updatedCells: { type: 'number', description: 'Number of cells updated' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
|
||||
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import type {
|
||||
GoogleSheetsV2ToolParams,
|
||||
GoogleSheetsV2UpdateResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2UpdateResponse> = {
|
||||
id: 'google_sheets_update_v2',
|
||||
name: 'Update Google Sheets V2',
|
||||
description: 'Update data in a specific sheet in a Google Sheets spreadsheet',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet to update',
|
||||
},
|
||||
sheetName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the sheet/tab to update',
|
||||
},
|
||||
cellRange: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The cell range to update (e.g. "A1:D10", "A1"). Defaults to "A1" if not specified.',
|
||||
},
|
||||
values: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The data to update as a 2D array (e.g. [["Name", "Age"], ["Alice", 30]]) or array of objects.',
|
||||
},
|
||||
valueInputOption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The format of the data to update',
|
||||
},
|
||||
includeValuesInResponse: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Whether to include the updated values in the response',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const sheetName = params.sheetName?.trim()
|
||||
if (!sheetName) {
|
||||
throw new Error('Sheet name is required')
|
||||
}
|
||||
|
||||
const cellRange = params.cellRange?.trim() || 'A1'
|
||||
const fullRange = `${sheetName}!${cellRange}`
|
||||
|
||||
const url = new URL(
|
||||
`https://sheets.googleapis.com/v4/spreadsheets/${params.spreadsheetId}/values/${encodeURIComponent(fullRange)}`
|
||||
)
|
||||
|
||||
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
|
||||
url.searchParams.append('valueInputOption', valueInputOption)
|
||||
|
||||
if (params.includeValuesInResponse) {
|
||||
url.searchParams.append('includeValuesInResponse', 'true')
|
||||
}
|
||||
|
||||
return url.toString()
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let processedValues: any = params.values || []
|
||||
|
||||
// Minimal shape enforcement: Google requires a 2D array
|
||||
if (!Array.isArray(processedValues)) {
|
||||
processedValues = [[processedValues]]
|
||||
} else if (!processedValues.every((item: any) => Array.isArray(item))) {
|
||||
processedValues = (processedValues as any[]).map((row: any) =>
|
||||
Array.isArray(row) ? row : [row]
|
||||
)
|
||||
}
|
||||
|
||||
// Handle array of objects
|
||||
if (
|
||||
Array.isArray(processedValues) &&
|
||||
processedValues.length > 0 &&
|
||||
typeof processedValues[0] === 'object' &&
|
||||
!Array.isArray(processedValues[0])
|
||||
) {
|
||||
const allKeys = new Set<string>()
|
||||
processedValues.forEach((obj: any) => {
|
||||
if (obj && typeof obj === 'object') {
|
||||
Object.keys(obj).forEach((key) => allKeys.add(key))
|
||||
}
|
||||
})
|
||||
const headers = Array.from(allKeys)
|
||||
|
||||
const rows = processedValues.map((obj: any) => {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return Array(headers.length).fill('')
|
||||
}
|
||||
return headers.map((key) => {
|
||||
const value = obj[key]
|
||||
if (value !== null && typeof value === 'object') {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
return value === undefined ? '' : value
|
||||
})
|
||||
})
|
||||
|
||||
processedValues = [headers, ...rows]
|
||||
}
|
||||
|
||||
const body: Record<string, any> = {
|
||||
majorDimension: params.majorDimension || 'ROWS',
|
||||
values: processedValues,
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
|
||||
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
|
||||
|
||||
const metadata = {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
updatedRange: data.updatedRange ?? null,
|
||||
updatedRows: data.updatedRows ?? 0,
|
||||
updatedColumns: data.updatedColumns ?? 0,
|
||||
updatedCells: data.updatedCells ?? 0,
|
||||
metadata: {
|
||||
spreadsheetId: metadata.spreadsheetId,
|
||||
spreadsheetUrl: metadata.spreadsheetUrl,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
|
||||
updatedRows: { type: 'number', description: 'Number of rows updated' },
|
||||
updatedColumns: { type: 'number', description: 'Number of columns updated' },
|
||||
updatedCells: { type: 'number', description: 'Number of cells updated' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
|
||||
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { GoogleSheetsToolParams, GoogleSheetsWriteResponse } from '@/tools/google_sheets/types'
|
||||
import type {
|
||||
GoogleSheetsToolParams,
|
||||
GoogleSheetsV2ToolParams,
|
||||
GoogleSheetsV2WriteResponse,
|
||||
GoogleSheetsWriteResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const writeTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsWriteResponse> = {
|
||||
@@ -177,3 +182,175 @@ export const writeTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsWriteResp
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const writeV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2WriteResponse> = {
|
||||
id: 'google_sheets_write_v2',
|
||||
name: 'Write to Google Sheets V2',
|
||||
description: 'Write data to a specific sheet in a Google Sheets spreadsheet',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet',
|
||||
},
|
||||
sheetName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the sheet/tab to write to',
|
||||
},
|
||||
cellRange: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The cell range to write to (e.g. "A1:D10", "A1"). Defaults to "A1" if not specified.',
|
||||
},
|
||||
values: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The data to write as a 2D array (e.g. [["Name", "Age"], ["Alice", 30], ["Bob", 25]]) or array of objects.',
|
||||
},
|
||||
valueInputOption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The format of the data to write',
|
||||
},
|
||||
includeValuesInResponse: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Whether to include the written values in the response',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const sheetName = params.sheetName?.trim()
|
||||
if (!sheetName) {
|
||||
throw new Error('Sheet name is required')
|
||||
}
|
||||
|
||||
// Build the range: SheetName!CellRange or just SheetName!A1
|
||||
const cellRange = params.cellRange?.trim() || 'A1'
|
||||
const fullRange = `${sheetName}!${cellRange}`
|
||||
|
||||
const url = new URL(
|
||||
`https://sheets.googleapis.com/v4/spreadsheets/${params.spreadsheetId}/values/${encodeURIComponent(fullRange)}`
|
||||
)
|
||||
|
||||
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
|
||||
url.searchParams.append('valueInputOption', valueInputOption)
|
||||
|
||||
if (params.includeValuesInResponse) {
|
||||
url.searchParams.append('includeValuesInResponse', 'true')
|
||||
}
|
||||
|
||||
return url.toString()
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let processedValues: any = params.values || []
|
||||
|
||||
// Handle array of objects
|
||||
if (
|
||||
Array.isArray(processedValues) &&
|
||||
processedValues.length > 0 &&
|
||||
typeof processedValues[0] === 'object' &&
|
||||
!Array.isArray(processedValues[0])
|
||||
) {
|
||||
const allKeys = new Set<string>()
|
||||
processedValues.forEach((obj: any) => {
|
||||
if (obj && typeof obj === 'object') {
|
||||
Object.keys(obj).forEach((key) => allKeys.add(key))
|
||||
}
|
||||
})
|
||||
const headers = Array.from(allKeys)
|
||||
|
||||
const rows = processedValues.map((obj: any) => {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return Array(headers.length).fill('')
|
||||
}
|
||||
return headers.map((key) => {
|
||||
const value = obj[key]
|
||||
if (value !== null && typeof value === 'object') {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
return value === undefined ? '' : value
|
||||
})
|
||||
})
|
||||
|
||||
processedValues = [headers, ...rows]
|
||||
}
|
||||
|
||||
const body: Record<string, any> = {
|
||||
majorDimension: params.majorDimension || 'ROWS',
|
||||
values: processedValues,
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
|
||||
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
|
||||
|
||||
const metadata = {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
updatedRange: data.updatedRange ?? null,
|
||||
updatedRows: data.updatedRows ?? 0,
|
||||
updatedColumns: data.updatedColumns ?? 0,
|
||||
updatedCells: data.updatedCells ?? 0,
|
||||
metadata: {
|
||||
spreadsheetId: metadata.spreadsheetId,
|
||||
spreadsheetUrl: metadata.spreadsheetUrl,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
|
||||
updatedRows: { type: 'number', description: 'Number of rows updated' },
|
||||
updatedColumns: { type: 'number', description: 'Number of columns updated' },
|
||||
updatedCells: { type: 'number', description: 'Number of cells updated' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
|
||||
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
import type {
|
||||
GoogleSheetsV2ToolParams,
|
||||
GoogleSheetsV2WriteResponse,
|
||||
} from '@/tools/google_sheets/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const writeV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2WriteResponse> = {
|
||||
id: 'google_sheets_write_v2',
|
||||
name: 'Write to Google Sheets V2',
|
||||
description: 'Write data to a specific sheet in a Google Sheets spreadsheet',
|
||||
version: '2.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-sheets',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Sheets API',
|
||||
},
|
||||
spreadsheetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the spreadsheet',
|
||||
},
|
||||
sheetName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the sheet/tab to write to',
|
||||
},
|
||||
cellRange: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The cell range to write to (e.g. "A1:D10", "A1"). Defaults to "A1" if not specified.',
|
||||
},
|
||||
values: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The data to write as a 2D array (e.g. [["Name", "Age"], ["Alice", 30], ["Bob", 25]]) or array of objects.',
|
||||
},
|
||||
valueInputOption: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The format of the data to write',
|
||||
},
|
||||
includeValuesInResponse: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Whether to include the written values in the response',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const sheetName = params.sheetName?.trim()
|
||||
if (!sheetName) {
|
||||
throw new Error('Sheet name is required')
|
||||
}
|
||||
|
||||
// Build the range: SheetName!CellRange or just SheetName!A1
|
||||
const cellRange = params.cellRange?.trim() || 'A1'
|
||||
const fullRange = `${sheetName}!${cellRange}`
|
||||
|
||||
const url = new URL(
|
||||
`https://sheets.googleapis.com/v4/spreadsheets/${params.spreadsheetId}/values/${encodeURIComponent(fullRange)}`
|
||||
)
|
||||
|
||||
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
|
||||
url.searchParams.append('valueInputOption', valueInputOption)
|
||||
|
||||
if (params.includeValuesInResponse) {
|
||||
url.searchParams.append('includeValuesInResponse', 'true')
|
||||
}
|
||||
|
||||
return url.toString()
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let processedValues: any = params.values || []
|
||||
|
||||
// Handle array of objects
|
||||
if (
|
||||
Array.isArray(processedValues) &&
|
||||
processedValues.length > 0 &&
|
||||
typeof processedValues[0] === 'object' &&
|
||||
!Array.isArray(processedValues[0])
|
||||
) {
|
||||
const allKeys = new Set<string>()
|
||||
processedValues.forEach((obj: any) => {
|
||||
if (obj && typeof obj === 'object') {
|
||||
Object.keys(obj).forEach((key) => allKeys.add(key))
|
||||
}
|
||||
})
|
||||
const headers = Array.from(allKeys)
|
||||
|
||||
const rows = processedValues.map((obj: any) => {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return Array(headers.length).fill('')
|
||||
}
|
||||
return headers.map((key) => {
|
||||
const value = obj[key]
|
||||
if (value !== null && typeof value === 'object') {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
return value === undefined ? '' : value
|
||||
})
|
||||
})
|
||||
|
||||
processedValues = [headers, ...rows]
|
||||
}
|
||||
|
||||
const body: Record<string, any> = {
|
||||
majorDimension: params.majorDimension || 'ROWS',
|
||||
values: processedValues,
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
|
||||
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
|
||||
|
||||
const metadata = {
|
||||
spreadsheetId,
|
||||
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
updatedRange: data.updatedRange ?? null,
|
||||
updatedRows: data.updatedRows ?? 0,
|
||||
updatedColumns: data.updatedColumns ?? 0,
|
||||
updatedCells: data.updatedCells ?? 0,
|
||||
metadata: {
|
||||
spreadsheetId: metadata.spreadsheetId,
|
||||
spreadsheetUrl: metadata.spreadsheetUrl,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
|
||||
updatedRows: { type: 'number', description: 'Number of rows updated' },
|
||||
updatedColumns: { type: 'number', description: 'Number of columns updated' },
|
||||
updatedCells: { type: 'number', description: 'Number of cells updated' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Spreadsheet metadata including ID and URL',
|
||||
properties: {
|
||||
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
|
||||
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user