diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index bacfe1e7fcc..6e8d1b46508 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4796,6 +4796,22 @@ export function GoogleGroupsIcon(props: SVGProps) { ) } +export function GoogleMeetIcon(props: SVGProps) { + return ( + + + + + + + + + ) +} + export function CursorIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 1e373d07d3e..c4bada328a4 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -58,6 +58,7 @@ import { GoogleGroupsIcon, GoogleIcon, GoogleMapsIcon, + GoogleMeetIcon, GooglePagespeedIcon, GoogleSheetsIcon, GoogleSlidesIcon, @@ -217,6 +218,7 @@ export const blockTypeToIconMap: Record = { google_forms: GoogleFormsIcon, google_groups: GoogleGroupsIcon, google_maps: GoogleMapsIcon, + google_meet: GoogleMeetIcon, google_pagespeed: GooglePagespeedIcon, google_search: GoogleIcon, google_sheets_v2: GoogleSheetsIcon, diff --git a/apps/docs/content/docs/en/tools/google_meet.mdx b/apps/docs/content/docs/en/tools/google_meet.mdx new file mode 100644 index 00000000000..f7b94f5db60 --- /dev/null +++ b/apps/docs/content/docs/en/tools/google_meet.mdx @@ -0,0 +1,156 @@ +--- +title: Google Meet +description: Create and manage Google Meet meetings +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* 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 > "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 > "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 | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index ff5cd4247cf..7bc7b9ecc81 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -52,6 +52,7 @@ "google_forms", "google_groups", "google_maps", + "google_meet", "google_pagespeed", "google_search", "google_sheets", diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx index c2ebf9b68be..9aa2451e37b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx @@ -55,6 +55,10 @@ const SCOPE_DESCRIPTIONS: Record = { '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', diff --git a/apps/sim/blocks/blocks/google_meet.ts b/apps/sim/blocks/blocks/google_meet.ts new file mode 100644 index 00000000000..b0524788dc8 --- /dev/null +++ b/apps/sim/blocks/blocks/google_meet.ts @@ -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 = { + 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 = { ...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' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 1794fb5afcc..1ce70983fcc 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -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 = { google_drive: GoogleDriveBlock, google_forms: GoogleFormsBlock, google_groups: GoogleGroupsBlock, + google_meet: GoogleMeetBlock, google_maps: GoogleMapsBlock, google_pagespeed: GooglePagespeedBlock, google_tasks: GoogleTasksBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index bacfe1e7fcc..6e8d1b46508 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -4796,6 +4796,22 @@ export function GoogleGroupsIcon(props: SVGProps) { ) } +export function GoogleMeetIcon(props: SVGProps) { + return ( + + + + + + + + + ) +} + export function CursorIcon(props: SVGProps) { return ( diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index 87beefef88b..17fa684c324 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -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, diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 652269afcba..2cb5f1f1156 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -16,6 +16,7 @@ import { GoogleFormsIcon, GoogleGroupsIcon, GoogleIcon, + GoogleMeetIcon, GoogleSheetsIcon, GoogleTasksIcon, HubspotIcon, @@ -168,6 +169,17 @@ export const OAUTH_PROVIDERS: Record = { '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.', diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index 2297c5df300..23cfb721630 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -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' diff --git a/apps/sim/tools/google_meet/create_space.ts b/apps/sim/tools/google_meet/create_space.ts new file mode 100644 index 00000000000..6bbd350a5fe --- /dev/null +++ b/apps/sim/tools/google_meet/create_space.ts @@ -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 = {} + + if (params.accessType || params.entryPointAccess) { + const config: Record = {} + 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, + }, + }, +} diff --git a/apps/sim/tools/google_meet/end_conference.ts b/apps/sim/tools/google_meet/end_conference.ts new file mode 100644 index 00000000000..355f1025174 --- /dev/null +++ b/apps/sim/tools/google_meet/end_conference.ts @@ -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' }, + }, +} diff --git a/apps/sim/tools/google_meet/get_conference_record.ts b/apps/sim/tools/google_meet/get_conference_record.ts new file mode 100644 index 00000000000..6f8e39f0abc --- /dev/null +++ b/apps/sim/tools/google_meet/get_conference_record.ts @@ -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' }, + }, +} diff --git a/apps/sim/tools/google_meet/get_space.ts b/apps/sim/tools/google_meet/get_space.ts new file mode 100644 index 00000000000..479e16ac3a7 --- /dev/null +++ b/apps/sim/tools/google_meet/get_space.ts @@ -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 = { + 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, + }, + }, +} diff --git a/apps/sim/tools/google_meet/index.ts b/apps/sim/tools/google_meet/index.ts new file mode 100644 index 00000000000..cb091cfd40b --- /dev/null +++ b/apps/sim/tools/google_meet/index.ts @@ -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 diff --git a/apps/sim/tools/google_meet/list_conference_records.ts b/apps/sim/tools/google_meet/list_conference_records.ts new file mode 100644 index 00000000000..cdf1eb933fa --- /dev/null +++ b/apps/sim/tools/google_meet/list_conference_records.ts @@ -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, + }, + }, +} diff --git a/apps/sim/tools/google_meet/list_participants.ts b/apps/sim/tools/google_meet/list_participants.ts new file mode 100644 index 00000000000..debcf530ddb --- /dev/null +++ b/apps/sim/tools/google_meet/list_participants.ts @@ -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 }, + }, +} diff --git a/apps/sim/tools/google_meet/types.ts b/apps/sim/tools/google_meet/types.ts new file mode 100644 index 00000000000..4f7630c2614 --- /dev/null +++ b/apps/sim/tools/google_meet/types.ts @@ -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 diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 26458f94f93..3cae7142412 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -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 = { 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,