Compare commits

...

7 Commits

Author SHA1 Message Date
waleed
993ee3001a lint 2026-02-27 11:18:41 -08:00
Waleed Latif
15c3fa4fb7 improvement(google-chat): validate tools, set optional fields to advanced mode 2026-02-27 11:02:41 -08:00
Waleed Latif
8d7d484355 Merge remote-tracking branch 'origin/staging' into waleedlatif1/add-google-chat
# Conflicts:
#	apps/docs/components/icons.tsx
#	apps/docs/components/ui/icon-mapping.ts
#	apps/docs/content/docs/en/tools/meta.json
#	apps/sim/blocks/registry.ts
#	apps/sim/components/icons.tsx
#	apps/sim/lib/auth/auth.ts
#	apps/sim/lib/oauth/oauth.ts
#	apps/sim/tools/registry.ts
2026-02-27 10:59:10 -08:00
Waleed Latif
f5c8c47fcf fix(google-chat): add OAuth provider registration to auth.ts 2026-02-25 13:54:07 -08:00
waleed
30cb108b30 lint 2026-02-25 13:26:57 -08:00
Waleed Latif
5cccde3301 fix(google-chat): use underscore in docsLink path 2026-02-25 13:06:12 -08:00
Waleed Latif
3d56ac9bb6 feat(google-chat): add Google Chat integration with OAuth 2026-02-25 13:06:12 -08:00
16 changed files with 563 additions and 0 deletions

View File

@@ -1346,6 +1346,37 @@ export function GoogleCalendarIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function GoogleChatIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 311 320' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fill='#0066da'
d='M76.37 0.51L76.38 76.98L0 76.96L0 20.77Q0.85 14.81 3.53 10.76Q10.14 0.74 22.75 0.67Q49.41 0.53 76.37 0.51Z'
/>
<path
fill='#fbbc04'
d='M76.37 0.51L233.79 0.53A1.61 1.57-26.7 0 1 234.71 0.82L235.08 1.09Q234.92 1.15 234.81 1.22Q234.64 1.31 234.64 1.5L234.62 77.01Q234.6 77.01 234.57 77.01L76.41 77.01Q76.4 77 76.38 76.98L76.37 0.51Z'
/>
<path
fill='#ea4335'
d='M235.08 1.09L310.53 76.77L234.62 77.01L234.64 1.5Q234.64 1.31 234.81 1.22Q234.92 1.15 235.08 1.09Z'
/>
<path
fill='#2684fc'
d='M0 76.96L76.38 76.98Q76.4 77 76.41 77.01L76.43 182.69L0 182.67L0 76.96Z'
/>
<path
fill='#00ac47'
d='M310.53 76.77L311 77.11L311 239.01Q308.34 253.54 295.94 257.78Q291.52 259.3 282.91 259.28Q227.02 259.19 169.99 259.11Q161.71 259.1 153.19 259.23Q152.72 259.24 152.39 259.57Q124.49 287.34 96.39 315.59C93.52 318.48 90.27 320.09 86.15 319.48Q80.39 318.63 77.66 313.54Q76.51 311.38 76.49 305.66Q76.42 282.47 76.44 259.13L76.43 220.85L114.21 183.07A1.79 1.77 22.3 0 1 115.47 182.55L233.77 182.59A0.83 0.83 0 0 0 234.6 181.76L234.57 77.01Q234.6 77.01 234.62 77.01L310.53 76.77Z'
/>
<path
fill='#00832d'
d='M76.43 182.69L76.43 220.85L76.44 259.13Q52.47 259.27 28.91 259.22Q19.09 259.2 14.76 257.68Q2.62 253.44 0 238.88L0 182.67L76.43 182.69Z'
/>
</svg>
)
}
export function GoogleTasksIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 527.1 500' xmlns='http://www.w3.org/2000/svg'>

View File

@@ -49,6 +49,7 @@ import {
GoogleBigQueryIcon,
GoogleBooksIcon,
GoogleCalendarIcon,
GoogleChatIcon,
GoogleContactsIcon,
GoogleDocsIcon,
GoogleDriveIcon,
@@ -205,6 +206,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
google_bigquery: GoogleBigQueryIcon,
google_books: GoogleBooksIcon,
google_calendar_v2: GoogleCalendarIcon,
google_chat: GoogleChatIcon,
google_contacts: GoogleContactsIcon,
google_docs: GoogleDocsIcon,
google_drive: GoogleDriveIcon,

View File

@@ -0,0 +1,62 @@
---
title: Google Chat
description: Send messages and manage Google Chat spaces
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_chat"
color="#E0E0E0"
/>
## Usage Instructions
Integrate with Google Chat to send messages to spaces and list available spaces using OAuth.
## Tools
### `google_chat_send_message`
Send a message to a Google Chat space
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spaceId` | string | Yes | The Google Chat space ID \(e.g., spaces/AAAA1234\) |
| `message` | string | Yes | Message text to send |
| `threadKey` | string | No | Thread key for sending a threaded reply |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `messageName` | string | Google Chat message resource name |
| `spaceName` | string | Space the message was sent to |
| `threadName` | string | Thread resource name |
| `text` | string | Message text that was sent |
| `createTime` | string | Timestamp when the message was created |
### `google_chat_list_spaces`
List Google Chat spaces the user is a member of
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `pageSize` | number | No | Maximum number of spaces to return \(default 100, max 1000\) |
| `pageToken` | string | No | Token for fetching the next page of results |
| `filter` | string | No | Filter by space type \(e.g., spaceType = "SPACE", spaceType = "GROUP_CHAT" OR spaceType = "DIRECT_MESSAGE"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `spaces` | json | Array of Google Chat space objects |
| `nextPageToken` | string | Token for fetching the next page of results |

View File

@@ -44,6 +44,7 @@
"google_bigquery",
"google_books",
"google_calendar",
"google_chat",
"google_contacts",
"google_docs",
"google_drive",

View File

@@ -55,6 +55,8 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'https://www.googleapis.com/auth/admin.directory.group.readonly': 'View Google Workspace groups',
'https://www.googleapis.com/auth/admin.directory.group.member.readonly':
'View Google Workspace group memberships',
'https://www.googleapis.com/auth/chat.spaces.readonly': 'View Google Chat spaces',
'https://www.googleapis.com/auth/chat.messages.create': 'Send messages in Google Chat',
'https://www.googleapis.com/auth/cloud-platform':
'Full access to Google Cloud resources for Vertex AI',
'read:confluence-content.all': 'Read all Confluence content',

View File

@@ -0,0 +1,150 @@
import { GoogleChatIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { GoogleChatResponse } from '@/tools/google_chat/types'
export const GoogleChatBlock: BlockConfig<GoogleChatResponse> = {
type: 'google_chat',
name: 'Google Chat',
description: 'Send messages and manage Google Chat spaces',
authMode: AuthMode.OAuth,
longDescription:
'Integrate with Google Chat to send messages to spaces and list available spaces using OAuth.',
docsLink: 'https://docs.sim.ai/tools/google_chat',
category: 'tools',
bgColor: '#E0E0E0',
icon: GoogleChatIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Send Message', id: 'send_message' },
{ label: 'List Spaces', id: 'list_spaces' },
],
value: () => 'send_message',
},
{
id: 'credential',
title: 'Google Chat Account',
type: 'oauth-input',
canonicalParamId: 'oauthCredential',
mode: 'basic',
required: true,
serviceId: 'google-chat',
requiredScopes: [
'https://www.googleapis.com/auth/chat.spaces.readonly',
'https://www.googleapis.com/auth/chat.messages.create',
],
placeholder: 'Select Google account',
},
{
id: 'manualCredential',
title: 'Google Chat Account',
type: 'short-input',
canonicalParamId: 'oauthCredential',
mode: 'advanced',
placeholder: 'Enter credential ID',
required: true,
},
{
id: 'spaceId',
title: 'Space ID',
type: 'short-input',
placeholder: 'e.g., spaces/AAAA1234 or AAAA1234',
required: { field: 'operation', value: 'send_message' },
condition: { field: 'operation', value: 'send_message' },
},
{
id: 'message',
title: 'Message',
type: 'long-input',
placeholder: 'Enter your message',
required: { field: 'operation', value: 'send_message' },
condition: { field: 'operation', value: 'send_message' },
},
{
id: 'threadKey',
title: 'Thread Key',
type: 'short-input',
placeholder: 'Optional thread key for threaded replies',
condition: { field: 'operation', value: 'send_message' },
mode: 'advanced',
},
{
id: 'filter',
title: 'Filter',
type: 'short-input',
placeholder: 'e.g., spaceType = "SPACE"',
condition: { field: 'operation', value: 'list_spaces' },
mode: 'advanced',
},
{
id: 'pageSize',
title: 'Max Results',
type: 'short-input',
placeholder: 'Maximum spaces to return (default 100)',
condition: { field: 'operation', value: 'list_spaces' },
mode: 'advanced',
},
],
tools: {
access: ['google_chat_send_message', 'google_chat_list_spaces'],
config: {
tool: (params) => {
switch (params.operation) {
case 'send_message':
return 'google_chat_send_message'
case 'list_spaces':
return 'google_chat_list_spaces'
default:
throw new Error(`Invalid Google Chat operation: ${params.operation}`)
}
},
params: (params) => {
const { oauthCredential, operation, ...rest } = params
switch (operation) {
case 'send_message':
return {
oauthCredential,
spaceId: rest.spaceId,
message: rest.message,
threadKey: rest.threadKey,
}
case 'list_spaces':
return {
oauthCredential,
pageSize: rest.pageSize ? Number(rest.pageSize) : undefined,
filter: rest.filter,
}
default:
return { oauthCredential, ...rest }
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
oauthCredential: { type: 'string', description: 'Google Chat OAuth credential' },
spaceId: { type: 'string', description: 'Google Chat space ID' },
message: { type: 'string', description: 'Message text to send' },
threadKey: { type: 'string', description: 'Thread key for threaded replies' },
filter: { type: 'string', description: 'Filter by space type' },
pageSize: { type: 'number', description: 'Maximum number of spaces to return' },
},
outputs: {
messageName: { type: 'string', description: 'Message resource name' },
spaceName: { type: 'string', description: 'Space resource name' },
threadName: { type: 'string', description: 'Thread resource name' },
text: { type: 'string', description: 'Message text that was sent' },
createTime: { type: 'string', description: 'Message creation timestamp' },
spaces: {
type: 'json',
description:
'Array of Google Chat space objects (name, displayName, spaceType, singleUserBotDm, threaded, type)',
},
nextPageToken: { type: 'string', description: 'Token for next page of results' },
},
}

View File

@@ -50,6 +50,7 @@ import { GoogleSearchBlock } from '@/blocks/blocks/google'
import { GoogleBigQueryBlock } from '@/blocks/blocks/google_bigquery'
import { GoogleBooksBlock } from '@/blocks/blocks/google_books'
import { GoogleCalendarBlock, GoogleCalendarV2Block } from '@/blocks/blocks/google_calendar'
import { GoogleChatBlock } from '@/blocks/blocks/google_chat'
import { GoogleContactsBlock } from '@/blocks/blocks/google_contacts'
import { GoogleDocsBlock } from '@/blocks/blocks/google_docs'
import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'
@@ -242,6 +243,7 @@ export const registry: Record<string, BlockConfig> = {
gmail: GmailBlock,
gmail_v2: GmailV2Block,
google_calendar: GoogleCalendarBlock,
google_chat: GoogleChatBlock,
google_calendar_v2: GoogleCalendarV2Block,
google_books: GoogleBooksBlock,
google_contacts: GoogleContactsBlock,

View File

@@ -1346,6 +1346,37 @@ export function GoogleCalendarIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function GoogleChatIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 311 320' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fill='#0066da'
d='M76.37 0.51L76.38 76.98L0 76.96L0 20.77Q0.85 14.81 3.53 10.76Q10.14 0.74 22.75 0.67Q49.41 0.53 76.37 0.51Z'
/>
<path
fill='#fbbc04'
d='M76.37 0.51L233.79 0.53A1.61 1.57-26.7 0 1 234.71 0.82L235.08 1.09Q234.92 1.15 234.81 1.22Q234.64 1.31 234.64 1.5L234.62 77.01Q234.6 77.01 234.57 77.01L76.41 77.01Q76.4 77 76.38 76.98L76.37 0.51Z'
/>
<path
fill='#ea4335'
d='M235.08 1.09L310.53 76.77L234.62 77.01L234.64 1.5Q234.64 1.31 234.81 1.22Q234.92 1.15 235.08 1.09Z'
/>
<path
fill='#2684fc'
d='M0 76.96L76.38 76.98Q76.4 77 76.41 77.01L76.43 182.69L0 182.67L0 76.96Z'
/>
<path
fill='#00ac47'
d='M310.53 76.77L311 77.11L311 239.01Q308.34 253.54 295.94 257.78Q291.52 259.3 282.91 259.28Q227.02 259.19 169.99 259.11Q161.71 259.1 153.19 259.23Q152.72 259.24 152.39 259.57Q124.49 287.34 96.39 315.59C93.52 318.48 90.27 320.09 86.15 319.48Q80.39 318.63 77.66 313.54Q76.51 311.38 76.49 305.66Q76.42 282.47 76.44 259.13L76.43 220.85L114.21 183.07A1.79 1.77 22.3 0 1 115.47 182.55L233.77 182.59A0.83 0.83 0 0 0 234.6 181.76L234.57 77.01Q234.6 77.01 234.62 77.01L310.53 76.77Z'
/>
<path
fill='#00832d'
d='M76.43 182.69L76.43 220.85L76.44 259.13Q52.47 259.27 28.91 259.22Q19.09 259.2 14.76 257.68Q2.62 253.44 0 238.88L0 182.67L76.43 182.69Z'
/>
</svg>
)
}
export function GoogleTasksIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 527.1 500' xmlns='http://www.w3.org/2000/svg'>

View File

@@ -488,6 +488,7 @@ export const auth = betterAuth({
'google-bigquery',
'google-vault',
'google-groups',
'google-chat',
'google-tasks',
'vertex-ai',
'github-repo',
@@ -1232,6 +1233,47 @@ export const auth = betterAuth({
},
},
{
providerId: 'google-chat',
clientId: env.GOOGLE_CLIENT_ID as string,
clientSecret: env.GOOGLE_CLIENT_SECRET as string,
discoveryUrl: 'https://accounts.google.com/.well-known/openid-configuration',
accessType: 'offline',
scopes: [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/chat.spaces.readonly',
'https://www.googleapis.com/auth/chat.messages.create',
],
prompt: 'consent',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/google-chat`,
getUserInfo: async (tokens) => {
try {
const response = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
headers: { Authorization: `Bearer ${tokens.accessToken}` },
})
if (!response.ok) {
logger.error('Failed to fetch Google user info', { status: response.status })
throw new Error(`Failed to fetch Google user info: ${response.statusText}`)
}
const profile = await response.json()
const now = new Date()
return {
id: `${profile.sub}-${crypto.randomUUID()}`,
name: profile.name || 'Google User',
email: profile.email,
image: profile.picture || undefined,
emailVerified: profile.email_verified || false,
createdAt: now,
updatedAt: now,
}
} catch (error) {
logger.error('Error in Google getUserInfo', { error })
throw error
}
},
},
{
providerId: 'google-tasks',
clientId: env.GOOGLE_CLIENT_ID as string,

View File

@@ -10,6 +10,7 @@ import {
GmailIcon,
GoogleBigQueryIcon,
GoogleCalendarIcon,
GoogleChatIcon,
GoogleContactsIcon,
GoogleDocsIcon,
GoogleDriveIcon,
@@ -168,6 +169,17 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
'https://www.googleapis.com/auth/admin.directory.group.member',
],
},
'google-chat': {
name: 'Google Chat',
description: 'Send messages and manage Google Chat spaces.',
providerId: 'google-chat',
icon: GoogleChatIcon,
baseProviderIcon: GoogleIcon,
scopes: [
'https://www.googleapis.com/auth/chat.spaces.readonly',
'https://www.googleapis.com/auth/chat.messages.create',
],
},
'vertex-ai': {
name: 'Vertex AI',
description: 'Access Google Cloud Vertex AI for Gemini models with OAuth.',

View File

@@ -13,6 +13,7 @@ export type OAuthProvider =
| 'google-vault'
| 'google-forms'
| 'google-groups'
| 'google-chat'
| 'vertex-ai'
| 'github'
| 'github-repo'
@@ -61,6 +62,7 @@ export type OAuthService =
| 'google-vault'
| 'google-forms'
| 'google-groups'
| 'google-chat'
| 'vertex-ai'
| 'github'
| 'x'

View File

@@ -0,0 +1,6 @@
import { listSpacesTool } from './list_spaces'
import { sendMessageTool } from './send_message'
export const googleChatSendMessageTool = sendMessageTool
export const googleChatListSpacesTool = listSpacesTool
export * from './types'

View File

@@ -0,0 +1,89 @@
import type { GoogleChatListSpacesParams, GoogleChatResponse } from '@/tools/google_chat/types'
import type { ToolConfig } from '@/tools/types'
export const listSpacesTool: ToolConfig<GoogleChatListSpacesParams, GoogleChatResponse> = {
id: 'google_chat_list_spaces',
name: 'Google Chat List Spaces',
description: 'List Google Chat spaces the user is a member of',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-chat',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of spaces to return (default 100, max 1000)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Token for fetching the next page of results',
},
filter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Filter by space type (e.g., spaceType = "SPACE", spaceType = "GROUP_CHAT" OR spaceType = "DIRECT_MESSAGE")',
},
},
request: {
url: (params) => {
const url = new URL('https://chat.googleapis.com/v1/spaces')
if (params.pageSize) {
url.searchParams.set('pageSize', String(params.pageSize))
}
if (params.pageToken) {
url.searchParams.set('pageToken', params.pageToken)
}
if (params.filter) {
url.searchParams.set('filter', params.filter)
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to list spaces')
}
return {
success: true,
output: {
spaces: data.spaces ?? [],
nextPageToken: data.nextPageToken ?? null,
},
}
},
outputs: {
spaces: {
type: 'json',
description:
'Array of Google Chat space objects (name, displayName, spaceType, singleUserBotDm, threaded, type)',
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of results',
optional: true,
},
},
}

View File

@@ -0,0 +1,95 @@
import type { GoogleChatResponse, GoogleChatSendMessageParams } from '@/tools/google_chat/types'
import type { ToolConfig } from '@/tools/types'
export const sendMessageTool: ToolConfig<GoogleChatSendMessageParams, GoogleChatResponse> = {
id: 'google_chat_send_message',
name: 'Google Chat Send Message',
description: 'Send a message to a Google Chat space',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-chat',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
spaceId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Google Chat space ID (e.g., spaces/AAAA1234)',
},
message: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Message text to send',
},
threadKey: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Thread key for sending a threaded reply',
},
},
request: {
url: (params) => {
const spaceId = params.spaceId?.trim()
if (!spaceId) {
throw new Error('Space ID is required')
}
const spaceName = spaceId.startsWith('spaces/') ? spaceId : `spaces/${spaceId}`
const url = new URL(`https://chat.googleapis.com/v1/${spaceName}/messages`)
if (params.threadKey) {
url.searchParams.set('messageReplyOption', 'REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD')
}
return url.toString()
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
text: params.message,
}
if (params.threadKey) {
body.thread = { threadKey: params.threadKey }
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to send message')
}
return {
success: true,
output: {
messageName: data.name ?? null,
spaceName: data.space?.name ?? null,
threadName: data.thread?.name ?? null,
text: data.text ?? null,
createTime: data.createTime ?? null,
},
}
},
outputs: {
messageName: { type: 'string', description: 'Google Chat message resource name' },
spaceName: { type: 'string', description: 'Space the message was sent to' },
threadName: { type: 'string', description: 'Thread resource name', optional: true },
text: { type: 'string', description: 'Message text that was sent' },
createTime: { type: 'string', description: 'Timestamp when the message was created' },
},
}

View File

@@ -0,0 +1,33 @@
import type { ToolResponse } from '@/tools/types'
/**
* Common parameters for Google Chat API calls
*/
export interface GoogleChatCommonParams {
accessToken: string
}
/**
* Parameters for sending a message to a Google Chat space
*/
export interface GoogleChatSendMessageParams extends GoogleChatCommonParams {
spaceId: string
message: string
threadKey?: string
}
/**
* Parameters for listing Google Chat spaces
*/
export interface GoogleChatListSpacesParams extends GoogleChatCommonParams {
pageSize?: number
pageToken?: string
filter?: string
}
/**
* Standard response for Google Chat operations
*/
export interface GoogleChatResponse extends ToolResponse {
output: Record<string, unknown>
}

View File

@@ -714,6 +714,7 @@ import {
googleCalendarUpdateTool,
googleCalendarUpdateV2Tool,
} from '@/tools/google_calendar'
import { googleChatListSpacesTool, googleChatSendMessageTool } from '@/tools/google_chat'
import {
googleContactsCreateTool,
googleContactsDeleteTool,
@@ -3150,6 +3151,8 @@ export const tools: Record<string, ToolConfig> = {
google_docs_create: googleDocsCreateTool,
google_books_volume_search: googleBooksVolumeSearchTool,
google_books_volume_details: googleBooksVolumeDetailsTool,
google_chat_list_spaces: googleChatListSpacesTool,
google_chat_send_message: googleChatSendMessageTool,
google_maps_air_quality: googleMapsAirQualityTool,
google_maps_directions: googleMapsDirectionsTool,
google_maps_distance_matrix: googleMapsDistanceMatrixTool,