feat(google-chat): add Google Chat integration with OAuth

This commit is contained in:
Waleed Latif
2026-02-25 12:06:23 -08:00
parent 870d4b55c6
commit 3d56ac9bb6
15 changed files with 533 additions and 7 deletions

View File

@@ -1302,6 +1302,42 @@ 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 SupabaseIcon(props: SVGProps<SVGSVGElement>) {
const id = useId()
const gradient0 = `supabase_paint0_${id}`

View File

@@ -38,12 +38,13 @@ import {
EyeIcon,
FirecrawlIcon,
FirefliesIcon,
GithubIcon,
GitLabIcon,
GithubIcon,
GmailIcon,
GongIcon,
GoogleBooksIcon,
GoogleCalendarIcon,
GoogleChatIcon,
GoogleDocsIcon,
GoogleDriveIcon,
GoogleFormsIcon,
@@ -72,9 +73,9 @@ import {
LinearIcon,
LinkedInIcon,
LinkupIcon,
MailServerIcon,
MailchimpIcon,
MailgunIcon,
MailServerIcon,
Mem0Icon,
MicrosoftDataverseIcon,
MicrosoftExcelIcon,
@@ -107,6 +108,8 @@ import {
ResendIcon,
RevenueCatIcon,
S3Icon,
SQSIcon,
STTIcon,
SalesforceIcon,
SearchIcon,
SendgridIcon,
@@ -118,19 +121,17 @@ import {
SimilarwebIcon,
SlackIcon,
SmtpIcon,
SQSIcon,
SshIcon,
STTIcon,
StagehandIcon,
StripeIcon,
SupabaseIcon,
TTSIcon,
TavilyIcon,
TelegramIcon,
TextractIcon,
TinybirdIcon,
TranslateIcon,
TrelloIcon,
TTSIcon,
TwilioIcon,
TypeformIcon,
UpstashIcon,
@@ -141,11 +142,11 @@ import {
WhatsAppIcon,
WikipediaIcon,
WordpressIcon,
xIcon,
YouTubeIcon,
ZendeskIcon,
ZepIcon,
ZoomIcon,
xIcon,
} from '@/components/icons'
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
@@ -189,6 +190,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
gong: GongIcon,
google_books: GoogleBooksIcon,
google_calendar_v2: GoogleCalendarIcon,
google_chat: GoogleChatIcon,
google_docs: GoogleDocsIcon,
google_drive: GoogleDriveIcon,
google_forms: GoogleFormsIcon,

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

@@ -39,6 +39,7 @@
"gong",
"google_books",
"google_calendar",
"google_chat",
"google_docs",
"google_drive",
"google_forms",
@@ -146,4 +147,4 @@
"zep",
"zoom"
]
}
}

View File

@@ -52,6 +52,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,143 @@
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' },
},
{
id: 'filter',
title: 'Filter',
type: 'short-input',
placeholder: 'e.g., spaceType = "SPACE"',
condition: { field: 'operation', value: 'list_spaces' },
},
{
id: 'pageSize',
title: 'Max Results',
type: 'short-input',
placeholder: 'Maximum spaces to return (default 100)',
condition: { field: 'operation', value: 'list_spaces' },
},
],
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' },
nextPageToken: { type: 'string', description: 'Token for next page of results' },
},
}

View File

@@ -45,6 +45,7 @@ import { GongBlock } from '@/blocks/blocks/gong'
import { GoogleSearchBlock } from '@/blocks/blocks/google'
import { GoogleBooksBlock } from '@/blocks/blocks/google_books'
import { GoogleCalendarBlock, GoogleCalendarV2Block } from '@/blocks/blocks/google_calendar'
import { GoogleChatBlock } from '@/blocks/blocks/google_chat'
import { GoogleDocsBlock } from '@/blocks/blocks/google_docs'
import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'
import { GoogleFormsBlock } from '@/blocks/blocks/google_forms'
@@ -227,6 +228,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_docs: GoogleDocsBlock,

View File

@@ -1302,6 +1302,42 @@ 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 SupabaseIcon(props: SVGProps<SVGSVGElement>) {
const id = useId()
const gradient0 = `supabase_paint0_${id}`

View File

@@ -12,6 +12,7 @@ import {
GoogleDocsIcon,
GoogleDriveIcon,
GoogleFormsIcon,
GoogleChatIcon,
GoogleGroupsIcon,
GoogleIcon,
GoogleSheetsIcon,
@@ -141,6 +142,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

@@ -10,6 +10,7 @@ export type OAuthProvider =
| 'google-vault'
| 'google-forms'
| 'google-groups'
| 'google-chat'
| 'vertex-ai'
| 'github'
| 'github-repo'
@@ -55,6 +56,7 @@ export type OAuthService =
| 'google-vault'
| 'google-forms'
| 'google-groups'
| 'google-chat'
| 'vertex-ai'
| 'github'
| 'x'

View File

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

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}`,
'Content-Type': 'application/json',
}),
},
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',
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of results',
optional: true,
},
},
}

View File

@@ -0,0 +1,98 @@
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

@@ -621,6 +621,7 @@ import {
} from '@/tools/gong'
import { googleSearchTool } from '@/tools/google'
import { googleBooksVolumeDetailsTool, googleBooksVolumeSearchTool } from '@/tools/google_books'
import { googleChatListSpacesTool, googleChatSendMessageTool } from '@/tools/google_chat'
import {
googleCalendarCreateTool,
googleCalendarCreateV2Tool,
@@ -2878,6 +2879,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,