feat(integrations): add google meet integration (#3403)

* feat(integrations): add google meet integration

* lint

* ack comments
This commit is contained in:
Waleed
2026-03-02 21:59:09 -08:00
committed by GitHub
parent a8e0203a92
commit f0ee492ada
20 changed files with 1186 additions and 0 deletions

View File

@@ -4796,6 +4796,22 @@ export function GoogleGroupsIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function GoogleMeetIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 87.5 72'>
<path fill='#00832d' d='M49.5 36l8.53 9.75 11.47 7.33 2-17.02-2-16.64-11.69 6.44z' />
<path fill='#0066da' d='M0 51.5V66c0 3.315 2.685 6 6 6h14.5l3-10.96-3-9.54-9.95-3z' />
<path fill='#e94235' d='M20.5 0L0 20.5l10.55 3 9.95-3 2.95-9.41z' />
<path fill='#2684fc' d='M20.5 20.5H0v31h20.5z' />
<path
fill='#00ac47'
d='M82.6 8.68L69.5 19.42v33.66l13.16 10.79c1.97 1.54 4.85.135 4.85-2.37V11c0-2.535-2.945-3.925-4.91-2.32zM49.5 36v15.5h-29V72h43c3.315 0 6-2.685 6-6V53.08z'
/>
<path fill='#ffba00' d='M63.5 0h-43v20.5h29V36l20-16.57V6c0-3.315-2.685-6-6-6z' />
</svg>
)
}
export function CursorIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 546 546' fill='currentColor'>

View File

@@ -58,6 +58,7 @@ import {
GoogleGroupsIcon,
GoogleIcon,
GoogleMapsIcon,
GoogleMeetIcon,
GooglePagespeedIcon,
GoogleSheetsIcon,
GoogleSlidesIcon,
@@ -217,6 +218,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
google_forms: GoogleFormsIcon,
google_groups: GoogleGroupsIcon,
google_maps: GoogleMapsIcon,
google_meet: GoogleMeetIcon,
google_pagespeed: GooglePagespeedIcon,
google_search: GoogleIcon,
google_sheets_v2: GoogleSheetsIcon,

View File

@@ -0,0 +1,156 @@
---
title: Google Meet
description: Create and manage Google Meet meetings
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_meet"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Google Meet](https://meet.google.com) is Google's video conferencing and online meeting platform, providing secure, high-quality video calls for individuals and teams. As a core component of Google Workspace, Google Meet enables real-time collaboration through video meetings, screen sharing, and integrated chat.
The Google Meet REST API (v2) allows programmatic management of meeting spaces and conference records, enabling automated workflows to create meetings, track participation, and manage active conferences without manual intervention.
Key features of the Google Meet API include:
- **Meeting Space Management**: Create, retrieve, and configure meeting spaces with customizable access controls.
- **Conference Records**: Access historical conference data including start/end times and associated spaces.
- **Participant Tracking**: View participant details for any conference including join/leave times and user types.
- **Access Controls**: Configure who can join meetings (open, trusted, or restricted) and which entry points are allowed.
- **Active Conference Management**: Programmatically end active conferences in meeting spaces.
In Sim, the Google Meet integration allows your agents to create meeting spaces on demand, monitor conference activity, track participation across meetings, and manage active conferences as part of automated workflows. This enables scenarios such as automatically provisioning meeting rooms for scheduled events, generating attendance reports, ending stale conferences, and building meeting analytics dashboards.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Google Meet into your workflow. Create meeting spaces, get space details, end conferences, list conference records, and view participants.
## Tools
### `google_meet_create_space`
Create a new Google Meet meeting space
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `accessType` | string | No | Who can join the meeting without knocking: OPEN \(anyone with link\), TRUSTED \(org members\), RESTRICTED \(only invited\) |
| `entryPointAccess` | string | No | Entry points allowed: ALL \(all entry points\) or CREATOR_APP_ONLY \(only via app\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `name` | string | Resource name of the space \(e.g., spaces/abc123\) |
| `meetingUri` | string | Meeting URL \(e.g., https://meet.google.com/abc-defg-hij\) |
| `meetingCode` | string | Meeting code \(e.g., abc-defg-hij\) |
| `accessType` | string | Access type configuration |
| `entryPointAccess` | string | Entry point access configuration |
### `google_meet_get_space`
Get details of a Google Meet meeting space by name or meeting code
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spaceName` | string | Yes | Space resource name \(spaces/abc123\) or meeting code \(abc-defg-hij\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `name` | string | Resource name of the space |
| `meetingUri` | string | Meeting URL |
| `meetingCode` | string | Meeting code |
| `accessType` | string | Access type configuration |
| `entryPointAccess` | string | Entry point access configuration |
| `activeConference` | string | Active conference record name |
### `google_meet_end_conference`
End the active conference in a Google Meet space
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spaceName` | string | Yes | Space resource name \(e.g., spaces/abc123\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ended` | boolean | Whether the conference was ended successfully |
### `google_meet_list_conference_records`
List conference records for meetings you organized
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `filter` | string | No | Filter by space name \(e.g., space.name = "spaces/abc123"\) or time range \(e.g., start_time &gt; "2024-01-01T00:00:00Z"\) |
| `pageSize` | number | No | Maximum number of conference records to return \(max 100\) |
| `pageToken` | string | No | Page token from a previous list request |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `conferenceRecords` | json | List of conference records with name, start/end times, and space |
| `nextPageToken` | string | Token for next page of results |
### `google_meet_get_conference_record`
Get details of a specific conference record
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `conferenceName` | string | Yes | Conference record resource name \(e.g., conferenceRecords/abc123\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `name` | string | Conference record resource name |
| `startTime` | string | Conference start time |
| `endTime` | string | Conference end time |
| `expireTime` | string | Conference record expiration time |
| `space` | string | Associated space resource name |
### `google_meet_list_participants`
List participants of a conference record
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `conferenceName` | string | Yes | Conference record resource name \(e.g., conferenceRecords/abc123\) |
| `filter` | string | No | Filter participants \(e.g., earliest_start_time &gt; "2024-01-01T00:00:00Z"\) |
| `pageSize` | number | No | Maximum number of participants to return \(default 100, max 250\) |
| `pageToken` | string | No | Page token from a previous list request |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `participants` | json | List of participants with name, times, display name, and user type |
| `nextPageToken` | string | Token for next page of results |
| `totalSize` | number | Total number of participants |

View File

@@ -52,6 +52,7 @@
"google_forms",
"google_groups",
"google_maps",
"google_meet",
"google_pagespeed",
"google_search",
"google_sheets",

View File

@@ -55,6 +55,10 @@ 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/meetings.space.created':
'Create and manage Google Meet meeting spaces',
'https://www.googleapis.com/auth/meetings.space.readonly':
'View Google Meet meeting space details',
'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,182 @@
import { GoogleMeetIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { GoogleMeetResponse } from '@/tools/google_meet/types'
export const GoogleMeetBlock: BlockConfig<GoogleMeetResponse> = {
type: 'google_meet',
name: 'Google Meet',
description: 'Create and manage Google Meet meetings',
longDescription:
'Integrate Google Meet into your workflow. Create meeting spaces, get space details, end conferences, list conference records, and view participants.',
docsLink: 'https://docs.sim.ai/tools/google_meet',
category: 'tools',
bgColor: '#E0E0E0',
icon: GoogleMeetIcon,
authMode: AuthMode.OAuth,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Create Space', id: 'create_space' },
{ label: 'Get Space', id: 'get_space' },
{ label: 'End Conference', id: 'end_conference' },
{ label: 'List Conference Records', id: 'list_conference_records' },
{ label: 'Get Conference Record', id: 'get_conference_record' },
{ label: 'List Participants', id: 'list_participants' },
],
value: () => 'create_space',
},
{
id: 'credential',
title: 'Google Meet Account',
type: 'oauth-input',
canonicalParamId: 'oauthCredential',
mode: 'basic',
required: true,
serviceId: 'google-meet',
requiredScopes: [
'https://www.googleapis.com/auth/meetings.space.created',
'https://www.googleapis.com/auth/meetings.space.readonly',
],
placeholder: 'Select Google Meet account',
},
{
id: 'manualCredential',
title: 'Google Meet Account',
type: 'short-input',
canonicalParamId: 'oauthCredential',
mode: 'advanced',
placeholder: 'Enter credential ID',
required: true,
},
// Create Space Fields
{
id: 'accessType',
title: 'Access Type',
type: 'dropdown',
condition: { field: 'operation', value: 'create_space' },
options: [
{ label: 'Open (anyone with link)', id: 'OPEN' },
{ label: 'Trusted (organization members)', id: 'TRUSTED' },
{ label: 'Restricted (invited only)', id: 'RESTRICTED' },
],
},
{
id: 'entryPointAccess',
title: 'Entry Point Access',
type: 'dropdown',
condition: { field: 'operation', value: 'create_space' },
mode: 'advanced',
options: [
{ label: 'All entry points', id: 'ALL' },
{ label: 'Creator app only', id: 'CREATOR_APP_ONLY' },
],
},
// Get Space / End Conference Fields
{
id: 'spaceName',
title: 'Space Name or Meeting Code',
type: 'short-input',
placeholder: 'spaces/abc123 or abc-defg-hij',
condition: { field: 'operation', value: ['get_space', 'end_conference'] },
required: { field: 'operation', value: ['get_space', 'end_conference'] },
},
// Conference Record Fields
{
id: 'conferenceName',
title: 'Conference Record Name',
type: 'short-input',
placeholder: 'conferenceRecords/abc123',
condition: { field: 'operation', value: ['get_conference_record', 'list_participants'] },
required: { field: 'operation', value: ['get_conference_record', 'list_participants'] },
},
// List Conference Records Fields
{
id: 'filter',
title: 'Filter',
type: 'short-input',
placeholder: 'space.name = "spaces/abc123"',
condition: { field: 'operation', value: ['list_conference_records', 'list_participants'] },
mode: 'advanced',
},
{
id: 'pageSize',
title: 'Page Size',
type: 'short-input',
placeholder: '25',
condition: { field: 'operation', value: ['list_conference_records', 'list_participants'] },
mode: 'advanced',
},
{
id: 'pageToken',
title: 'Page Token',
type: 'short-input',
placeholder: 'Token from previous request',
condition: { field: 'operation', value: ['list_conference_records', 'list_participants'] },
mode: 'advanced',
},
],
tools: {
access: [
'google_meet_create_space',
'google_meet_get_space',
'google_meet_end_conference',
'google_meet_list_conference_records',
'google_meet_get_conference_record',
'google_meet_list_participants',
],
config: {
tool: (params) => `google_meet_${params.operation}`,
params: (params) => {
const { oauthCredential, operation, pageSize, ...rest } = params
const processedParams: Record<string, unknown> = { ...rest }
if (pageSize) {
processedParams.pageSize =
typeof pageSize === 'string' ? Number.parseInt(pageSize, 10) : pageSize
}
return {
oauthCredential,
...processedParams,
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
oauthCredential: { type: 'string', description: 'Google Meet access token' },
accessType: { type: 'string', description: 'Access type for meeting space' },
entryPointAccess: { type: 'string', description: 'Entry point access setting' },
spaceName: { type: 'string', description: 'Space resource name or meeting code' },
conferenceName: { type: 'string', description: 'Conference record resource name' },
filter: { type: 'string', description: 'Filter expression' },
pageSize: { type: 'string', description: 'Maximum results per page' },
pageToken: { type: 'string', description: 'Pagination token' },
},
outputs: {
name: { type: 'string', description: 'Resource name' },
meetingUri: { type: 'string', description: 'Meeting URL' },
meetingCode: { type: 'string', description: 'Meeting code' },
accessType: { type: 'string', description: 'Access type' },
entryPointAccess: { type: 'string', description: 'Entry point access' },
activeConference: { type: 'string', description: 'Active conference record' },
ended: { type: 'boolean', description: 'Whether conference was ended' },
conferenceRecords: { type: 'json', description: 'List of conference records' },
startTime: { type: 'string', description: 'Conference start time' },
endTime: { type: 'string', description: 'Conference end time' },
expireTime: { type: 'string', description: 'Record expiration time' },
space: { type: 'string', description: 'Associated space name' },
participants: { type: 'json', description: 'List of participants' },
nextPageToken: { type: 'string', description: 'Next page token' },
totalSize: { type: 'number', description: 'Total participant count' },
},
}

View File

@@ -58,6 +58,7 @@ import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'
import { GoogleFormsBlock } from '@/blocks/blocks/google_forms'
import { GoogleGroupsBlock } from '@/blocks/blocks/google_groups'
import { GoogleMapsBlock } from '@/blocks/blocks/google_maps'
import { GoogleMeetBlock } from '@/blocks/blocks/google_meet'
import { GooglePagespeedBlock } from '@/blocks/blocks/google_pagespeed'
import { GoogleSheetsBlock, GoogleSheetsV2Block } from '@/blocks/blocks/google_sheets'
import { GoogleSlidesBlock, GoogleSlidesV2Block } from '@/blocks/blocks/google_slides'
@@ -255,6 +256,7 @@ export const registry: Record<string, BlockConfig> = {
google_drive: GoogleDriveBlock,
google_forms: GoogleFormsBlock,
google_groups: GoogleGroupsBlock,
google_meet: GoogleMeetBlock,
google_maps: GoogleMapsBlock,
google_pagespeed: GooglePagespeedBlock,
google_tasks: GoogleTasksBlock,

View File

@@ -4796,6 +4796,22 @@ export function GoogleGroupsIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function GoogleMeetIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 87.5 72'>
<path fill='#00832d' d='M49.5 36l8.53 9.75 11.47 7.33 2-17.02-2-16.64-11.69 6.44z' />
<path fill='#0066da' d='M0 51.5V66c0 3.315 2.685 6 6 6h14.5l3-10.96-3-9.54-9.95-3z' />
<path fill='#e94235' d='M20.5 0L0 20.5l10.55 3 9.95-3 2.95-9.41z' />
<path fill='#2684fc' d='M20.5 20.5H0v31h20.5z' />
<path
fill='#00ac47'
d='M82.6 8.68L69.5 19.42v33.66l13.16 10.79c1.97 1.54 4.85.135 4.85-2.37V11c0-2.535-2.945-3.925-4.91-2.32zM49.5 36v15.5h-29V72h43c3.315 0 6-2.685 6-6V53.08z'
/>
<path fill='#ffba00' d='M63.5 0h-43v20.5h29V36l20-16.57V6c0-3.315-2.685-6-6-6z' />
</svg>
)
}
export function CursorIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 546 546' fill='currentColor'>

View File

@@ -488,6 +488,7 @@ export const auth = betterAuth({
'google-bigquery',
'google-vault',
'google-groups',
'google-meet',
'google-tasks',
'vertex-ai',
'github-repo',
@@ -1243,6 +1244,47 @@ export const auth = betterAuth({
},
},
{
providerId: 'google-meet',
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/meetings.space.created',
'https://www.googleapis.com/auth/meetings.space.readonly',
],
prompt: 'consent',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/google-meet`,
getUserInfo: async (tokens) => {
try {
const response = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
headers: { Authorization: `Bearer ${tokens.accessToken}` },
})
if (!response.ok) {
await response.text().catch(() => {})
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

@@ -16,6 +16,7 @@ import {
GoogleFormsIcon,
GoogleGroupsIcon,
GoogleIcon,
GoogleMeetIcon,
GoogleSheetsIcon,
GoogleTasksIcon,
HubspotIcon,
@@ -168,6 +169,17 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
'https://www.googleapis.com/auth/admin.directory.group.member',
],
},
'google-meet': {
name: 'Google Meet',
description: 'Create and manage Google Meet meeting spaces and conferences.',
providerId: 'google-meet',
icon: GoogleMeetIcon,
baseProviderIcon: GoogleIcon,
scopes: [
'https://www.googleapis.com/auth/meetings.space.created',
'https://www.googleapis.com/auth/meetings.space.readonly',
],
},
'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-meet'
| 'vertex-ai'
| 'github'
| 'github-repo'
@@ -61,6 +62,7 @@ export type OAuthService =
| 'google-vault'
| 'google-forms'
| 'google-groups'
| 'google-meet'
| 'vertex-ai'
| 'github'
| 'x'

View File

@@ -0,0 +1,100 @@
import {
type GoogleMeetApiSpaceResponse,
type GoogleMeetCreateSpaceParams,
type GoogleMeetCreateSpaceResponse,
MEET_API_BASE,
} from '@/tools/google_meet/types'
import type { ToolConfig } from '@/tools/types'
export const createSpaceTool: ToolConfig<
GoogleMeetCreateSpaceParams,
GoogleMeetCreateSpaceResponse
> = {
id: 'google_meet_create_space',
name: 'Google Meet Create Space',
description: 'Create a new Google Meet meeting space',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-meet',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Meet API',
},
accessType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Who can join the meeting without knocking: OPEN (anyone with link), TRUSTED (org members), RESTRICTED (only invited)',
},
entryPointAccess: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Entry points allowed: ALL (all entry points) or CREATOR_APP_ONLY (only via app)',
},
},
request: {
url: () => `${MEET_API_BASE}/spaces`,
method: 'POST',
headers: (params: GoogleMeetCreateSpaceParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params: GoogleMeetCreateSpaceParams) => {
const body: Record<string, unknown> = {}
if (params.accessType || params.entryPointAccess) {
const config: Record<string, string> = {}
if (params.accessType) config.accessType = params.accessType
if (params.entryPointAccess) config.entryPointAccess = params.entryPointAccess
body.config = config
}
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.text().catch(() => 'Unknown error')
throw new Error(`Google Meet API error (${response.status}): ${error}`)
}
const data: GoogleMeetApiSpaceResponse = await response.json()
return {
success: true,
output: {
name: data.name,
meetingUri: data.meetingUri,
meetingCode: data.meetingCode,
accessType: data.config?.accessType ?? null,
entryPointAccess: data.config?.entryPointAccess ?? null,
},
}
},
outputs: {
name: { type: 'string', description: 'Resource name of the space (e.g., spaces/abc123)' },
meetingUri: {
type: 'string',
description: 'Meeting URL (e.g., https://meet.google.com/abc-defg-hij)',
},
meetingCode: { type: 'string', description: 'Meeting code (e.g., abc-defg-hij)' },
accessType: { type: 'string', description: 'Access type configuration', optional: true },
entryPointAccess: {
type: 'string',
description: 'Entry point access configuration',
optional: true,
},
},
}

View File

@@ -0,0 +1,67 @@
import {
type GoogleMeetEndConferenceParams,
type GoogleMeetEndConferenceResponse,
MEET_API_BASE,
} from '@/tools/google_meet/types'
import type { ToolConfig } from '@/tools/types'
export const endConferenceTool: ToolConfig<
GoogleMeetEndConferenceParams,
GoogleMeetEndConferenceResponse
> = {
id: 'google_meet_end_conference',
name: 'Google Meet End Conference',
description: 'End the active conference in a Google Meet space',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-meet',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Meet API',
},
spaceName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Space resource name (e.g., spaces/abc123)',
},
},
request: {
url: (params: GoogleMeetEndConferenceParams) => {
const trimmed = params.spaceName.trim()
const name = trimmed.startsWith('spaces/') ? trimmed : `spaces/${trimmed}`
return `${MEET_API_BASE}/${name}:endActiveConference`
},
method: 'POST',
headers: (params: GoogleMeetEndConferenceParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: () => ({}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.text().catch(() => 'Unknown error')
throw new Error(`Google Meet API error (${response.status}): ${error}`)
}
return {
success: true,
output: {
ended: true,
},
}
},
outputs: {
ended: { type: 'boolean', description: 'Whether the conference was ended successfully' },
},
}

View File

@@ -0,0 +1,78 @@
import {
type GoogleMeetApiConferenceRecordResponse,
type GoogleMeetGetConferenceRecordParams,
type GoogleMeetGetConferenceRecordResponse,
MEET_API_BASE,
} from '@/tools/google_meet/types'
import type { ToolConfig } from '@/tools/types'
export const getConferenceRecordTool: ToolConfig<
GoogleMeetGetConferenceRecordParams,
GoogleMeetGetConferenceRecordResponse
> = {
id: 'google_meet_get_conference_record',
name: 'Google Meet Get Conference Record',
description: 'Get details of a specific conference record',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-meet',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Meet API',
},
conferenceName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Conference record resource name (e.g., conferenceRecords/abc123)',
},
},
request: {
url: (params: GoogleMeetGetConferenceRecordParams) => {
const trimmed = params.conferenceName.trim()
const name = trimmed.startsWith('conferenceRecords/')
? trimmed
: `conferenceRecords/${trimmed}`
return `${MEET_API_BASE}/${name}`
},
method: 'GET',
headers: (params: GoogleMeetGetConferenceRecordParams) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.text().catch(() => 'Unknown error')
throw new Error(`Google Meet API error (${response.status}): ${error}`)
}
const data: GoogleMeetApiConferenceRecordResponse = await response.json()
return {
success: true,
output: {
name: data.name,
startTime: data.startTime,
endTime: data.endTime ?? null,
expireTime: data.expireTime,
space: data.space,
},
}
},
outputs: {
name: { type: 'string', description: 'Conference record resource name' },
startTime: { type: 'string', description: 'Conference start time' },
endTime: { type: 'string', description: 'Conference end time', optional: true },
expireTime: { type: 'string', description: 'Conference record expiration time' },
space: { type: 'string', description: 'Associated space resource name' },
},
}

View File

@@ -0,0 +1,83 @@
import {
type GoogleMeetApiSpaceResponse,
type GoogleMeetGetSpaceParams,
type GoogleMeetGetSpaceResponse,
MEET_API_BASE,
} from '@/tools/google_meet/types'
import type { ToolConfig } from '@/tools/types'
export const getSpaceTool: ToolConfig<GoogleMeetGetSpaceParams, GoogleMeetGetSpaceResponse> = {
id: 'google_meet_get_space',
name: 'Google Meet Get Space',
description: 'Get details of a Google Meet meeting space by name or meeting code',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-meet',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Meet API',
},
spaceName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Space resource name (spaces/abc123) or meeting code (abc-defg-hij)',
},
},
request: {
url: (params: GoogleMeetGetSpaceParams) => {
const trimmed = params.spaceName.trim()
const name = trimmed.startsWith('spaces/') ? trimmed : `spaces/${trimmed}`
return `${MEET_API_BASE}/${name}`
},
method: 'GET',
headers: (params: GoogleMeetGetSpaceParams) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.text().catch(() => 'Unknown error')
throw new Error(`Google Meet API error (${response.status}): ${error}`)
}
const data: GoogleMeetApiSpaceResponse = await response.json()
return {
success: true,
output: {
name: data.name,
meetingUri: data.meetingUri,
meetingCode: data.meetingCode,
accessType: data.config?.accessType ?? null,
entryPointAccess: data.config?.entryPointAccess ?? null,
activeConference: data.activeConference?.conferenceRecord ?? null,
},
}
},
outputs: {
name: { type: 'string', description: 'Resource name of the space' },
meetingUri: { type: 'string', description: 'Meeting URL' },
meetingCode: { type: 'string', description: 'Meeting code' },
accessType: { type: 'string', description: 'Access type configuration', optional: true },
entryPointAccess: {
type: 'string',
description: 'Entry point access configuration',
optional: true,
},
activeConference: {
type: 'string',
description: 'Active conference record name',
optional: true,
},
},
}

View File

@@ -0,0 +1,13 @@
import { createSpaceTool } from '@/tools/google_meet/create_space'
import { endConferenceTool } from '@/tools/google_meet/end_conference'
import { getConferenceRecordTool } from '@/tools/google_meet/get_conference_record'
import { getSpaceTool } from '@/tools/google_meet/get_space'
import { listConferenceRecordsTool } from '@/tools/google_meet/list_conference_records'
import { listParticipantsTool } from '@/tools/google_meet/list_participants'
export const googleMeetCreateSpaceTool = createSpaceTool
export const googleMeetGetSpaceTool = getSpaceTool
export const googleMeetEndConferenceTool = endConferenceTool
export const googleMeetListConferenceRecordsTool = listConferenceRecordsTool
export const googleMeetGetConferenceRecordTool = getConferenceRecordTool
export const googleMeetListParticipantsTool = listParticipantsTool

View File

@@ -0,0 +1,101 @@
import {
type GoogleMeetApiConferenceRecordListResponse,
type GoogleMeetListConferenceRecordsParams,
type GoogleMeetListConferenceRecordsResponse,
MEET_API_BASE,
} from '@/tools/google_meet/types'
import type { ToolConfig } from '@/tools/types'
export const listConferenceRecordsTool: ToolConfig<
GoogleMeetListConferenceRecordsParams,
GoogleMeetListConferenceRecordsResponse
> = {
id: 'google_meet_list_conference_records',
name: 'Google Meet List Conference Records',
description: 'List conference records for meetings you organized',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-meet',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Meet API',
},
filter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Filter by space name (e.g., space.name = "spaces/abc123") or time range (e.g., start_time > "2024-01-01T00:00:00Z")',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of conference records to return (max 100)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Page token from a previous list request',
},
},
request: {
url: (params: GoogleMeetListConferenceRecordsParams) => {
const queryParams = new URLSearchParams()
if (params.filter) queryParams.append('filter', params.filter)
if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString())
if (params.pageToken) queryParams.append('pageToken', params.pageToken)
const queryString = queryParams.toString()
return `${MEET_API_BASE}/conferenceRecords${queryString ? `?${queryString}` : ''}`
},
method: 'GET',
headers: (params: GoogleMeetListConferenceRecordsParams) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.text().catch(() => 'Unknown error')
throw new Error(`Google Meet API error (${response.status}): ${error}`)
}
const data: GoogleMeetApiConferenceRecordListResponse = await response.json()
const records = data.conferenceRecords ?? []
return {
success: true,
output: {
conferenceRecords: records.map((record) => ({
name: record.name,
startTime: record.startTime,
endTime: record.endTime ?? null,
expireTime: record.expireTime,
space: record.space,
})),
nextPageToken: data.nextPageToken ?? null,
},
}
},
outputs: {
conferenceRecords: {
type: 'json',
description: 'List of conference records with name, start/end times, and space',
},
nextPageToken: {
type: 'string',
description: 'Token for next page of results',
optional: true,
},
},
}

View File

@@ -0,0 +1,130 @@
import {
type GoogleMeetApiParticipantListResponse,
type GoogleMeetApiParticipantResponse,
type GoogleMeetListParticipantsParams,
type GoogleMeetListParticipantsResponse,
MEET_API_BASE,
} from '@/tools/google_meet/types'
import type { ToolConfig } from '@/tools/types'
export const listParticipantsTool: ToolConfig<
GoogleMeetListParticipantsParams,
GoogleMeetListParticipantsResponse
> = {
id: 'google_meet_list_participants',
name: 'Google Meet List Participants',
description: 'List participants of a conference record',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-meet',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Meet API',
},
conferenceName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Conference record resource name (e.g., conferenceRecords/abc123)',
},
filter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter participants (e.g., earliest_start_time > "2024-01-01T00:00:00Z")',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of participants to return (default 100, max 250)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Page token from a previous list request',
},
},
request: {
url: (params: GoogleMeetListParticipantsParams) => {
const trimmed = params.conferenceName.trim()
const name = trimmed.startsWith('conferenceRecords/')
? trimmed
: `conferenceRecords/${trimmed}`
const queryParams = new URLSearchParams()
if (params.filter) queryParams.append('filter', params.filter)
if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString())
if (params.pageToken) queryParams.append('pageToken', params.pageToken)
const queryString = queryParams.toString()
return `${MEET_API_BASE}/${name}/participants${queryString ? `?${queryString}` : ''}`
},
method: 'GET',
headers: (params: GoogleMeetListParticipantsParams) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.text().catch(() => 'Unknown error')
throw new Error(`Google Meet API error (${response.status}): ${error}`)
}
const data: GoogleMeetApiParticipantListResponse = await response.json()
const participants = data.participants ?? []
const getDisplayName = (p: GoogleMeetApiParticipantResponse): string | null => {
return (
p.signedinUser?.displayName ??
p.anonymousUser?.displayName ??
p.phoneUser?.displayName ??
null
)
}
const getUserType = (p: GoogleMeetApiParticipantResponse): string => {
if (p.signedinUser) return 'signed_in'
if (p.anonymousUser) return 'anonymous'
if (p.phoneUser) return 'phone'
return 'unknown'
}
return {
success: true,
output: {
participants: participants.map((p) => ({
name: p.name,
earliestStartTime: p.earliestStartTime,
latestEndTime: p.latestEndTime ?? null,
displayName: getDisplayName(p),
userType: getUserType(p),
})),
nextPageToken: data.nextPageToken ?? null,
totalSize: data.totalSize ?? null,
},
}
},
outputs: {
participants: {
type: 'json',
description: 'List of participants with name, times, display name, and user type',
},
nextPageToken: {
type: 'string',
description: 'Token for next page of results',
optional: true,
},
totalSize: { type: 'number', description: 'Total number of participants', optional: true },
},
}

View File

@@ -0,0 +1,165 @@
import type { ToolResponse } from '@/tools/types'
export const MEET_API_BASE = 'https://meet.googleapis.com/v2'
interface BaseGoogleMeetParams {
accessToken: string
}
export interface GoogleMeetCreateSpaceParams extends BaseGoogleMeetParams {
accessType?: 'OPEN' | 'TRUSTED' | 'RESTRICTED'
entryPointAccess?: 'ALL' | 'CREATOR_APP_ONLY'
}
export interface GoogleMeetGetSpaceParams extends BaseGoogleMeetParams {
spaceName: string
}
export interface GoogleMeetEndConferenceParams extends BaseGoogleMeetParams {
spaceName: string
}
export interface GoogleMeetListConferenceRecordsParams extends BaseGoogleMeetParams {
filter?: string
pageSize?: number
pageToken?: string
}
export interface GoogleMeetGetConferenceRecordParams extends BaseGoogleMeetParams {
conferenceName: string
}
export interface GoogleMeetListParticipantsParams extends BaseGoogleMeetParams {
conferenceName: string
filter?: string
pageSize?: number
pageToken?: string
}
export type GoogleMeetToolParams =
| GoogleMeetCreateSpaceParams
| GoogleMeetGetSpaceParams
| GoogleMeetEndConferenceParams
| GoogleMeetListConferenceRecordsParams
| GoogleMeetGetConferenceRecordParams
| GoogleMeetListParticipantsParams
export interface GoogleMeetApiSpaceResponse {
name: string
meetingUri: string
meetingCode: string
config?: {
accessType?: string
entryPointAccess?: string
}
activeConference?: {
conferenceRecord: string
}
}
export interface GoogleMeetApiConferenceRecordResponse {
name: string
startTime: string
endTime?: string
expireTime: string
space: string
}
export interface GoogleMeetApiConferenceRecordListResponse {
conferenceRecords: GoogleMeetApiConferenceRecordResponse[]
nextPageToken?: string
}
export interface GoogleMeetApiParticipantResponse {
name: string
earliestStartTime: string
latestEndTime?: string
signedinUser?: {
user: string
displayName: string
}
anonymousUser?: {
displayName: string
}
phoneUser?: {
displayName: string
}
}
export interface GoogleMeetApiParticipantListResponse {
participants: GoogleMeetApiParticipantResponse[]
nextPageToken?: string
totalSize?: number
}
export interface GoogleMeetCreateSpaceResponse extends ToolResponse {
output: {
name: string
meetingUri: string
meetingCode: string
accessType: string | null
entryPointAccess: string | null
}
}
export interface GoogleMeetGetSpaceResponse extends ToolResponse {
output: {
name: string
meetingUri: string
meetingCode: string
accessType: string | null
entryPointAccess: string | null
activeConference: string | null
}
}
export interface GoogleMeetEndConferenceResponse extends ToolResponse {
output: {
ended: boolean
}
}
export interface GoogleMeetListConferenceRecordsResponse extends ToolResponse {
output: {
conferenceRecords: Array<{
name: string
startTime: string
endTime: string | null
expireTime: string
space: string
}>
nextPageToken: string | null
}
}
export interface GoogleMeetGetConferenceRecordResponse extends ToolResponse {
output: {
name: string
startTime: string
endTime: string | null
expireTime: string
space: string
}
}
export interface GoogleMeetListParticipantsResponse extends ToolResponse {
output: {
participants: Array<{
name: string
earliestStartTime: string
latestEndTime: string | null
displayName: string | null
userType: string
}>
nextPageToken: string | null
totalSize: number | null
}
}
export type GoogleMeetResponse =
| GoogleMeetCreateSpaceResponse
| GoogleMeetGetSpaceResponse
| GoogleMeetEndConferenceResponse
| GoogleMeetListConferenceRecordsResponse
| GoogleMeetGetConferenceRecordResponse
| GoogleMeetListParticipantsResponse

View File

@@ -811,6 +811,14 @@ import {
googleMapsTimezoneTool,
googleMapsValidateAddressTool,
} from '@/tools/google_maps'
import {
googleMeetCreateSpaceTool,
googleMeetEndConferenceTool,
googleMeetGetConferenceRecordTool,
googleMeetGetSpaceTool,
googleMeetListConferenceRecordsTool,
googleMeetListParticipantsTool,
} from '@/tools/google_meet'
import { googlePagespeedAnalyzeTool } from '@/tools/google_pagespeed'
import {
googleSheetsAppendTool,
@@ -3215,6 +3223,12 @@ export const tools: Record<string, ToolConfig> = {
google_maps_speed_limits: googleMapsSpeedLimitsTool,
google_maps_timezone: googleMapsTimezoneTool,
google_maps_validate_address: googleMapsValidateAddressTool,
google_meet_create_space: googleMeetCreateSpaceTool,
google_meet_end_conference: googleMeetEndConferenceTool,
google_meet_get_conference_record: googleMeetGetConferenceRecordTool,
google_meet_get_space: googleMeetGetSpaceTool,
google_meet_list_conference_records: googleMeetListConferenceRecordsTool,
google_meet_list_participants: googleMeetListParticipantsTool,
google_pagespeed_analyze: googlePagespeedAnalyzeTool,
google_tasks_create: googleTasksCreateTool,
google_tasks_delete: googleTasksDeleteTool,