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:
Waleed
2026-01-17 20:51:15 -08:00
committed by GitHub
parent 19a8daedf7
commit ee7572185a
231 changed files with 19104 additions and 1925 deletions

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}

View 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' },
},
},
},
}

View 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' },
},
},
},
}

View 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' },
},
},
},
}

View File

@@ -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',

View 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',
},
},
}

View 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)' },
},
},
},
},
}

View 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' },
},
},
},
},
}

View File

@@ -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

View File

@@ -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' },
},
},
},
}

View File

@@ -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
}
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}