@openclaw/google-meet 2026.5.2 → 2026.5.3-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/config.ts DELETED
@@ -1,509 +0,0 @@
1
- import {
2
- REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME,
3
- resolveRealtimeVoiceAgentConsultToolPolicy,
4
- type RealtimeVoiceAgentConsultToolPolicy,
5
- } from "openclaw/plugin-sdk/realtime-voice";
6
- import {
7
- normalizeOptionalLowercaseString,
8
- normalizeOptionalString,
9
- } from "openclaw/plugin-sdk/text-runtime";
10
-
11
- export type GoogleMeetTransport = "chrome" | "chrome-node" | "twilio";
12
- export type GoogleMeetMode = "realtime" | "transcribe";
13
- type GoogleMeetChromeAudioFormat = "pcm16-24khz" | "g711-ulaw-8khz";
14
- export type GoogleMeetToolPolicy = RealtimeVoiceAgentConsultToolPolicy;
15
-
16
- export type GoogleMeetConfig = {
17
- enabled: boolean;
18
- defaults: {
19
- meeting?: string;
20
- };
21
- preview: {
22
- enrollmentAcknowledged: boolean;
23
- };
24
- defaultTransport: GoogleMeetTransport;
25
- defaultMode: GoogleMeetMode;
26
- chrome: {
27
- audioBackend: "blackhole-2ch";
28
- audioFormat: GoogleMeetChromeAudioFormat;
29
- launch: boolean;
30
- browserProfile?: string;
31
- guestName: string;
32
- reuseExistingTab: boolean;
33
- autoJoin: boolean;
34
- joinTimeoutMs: number;
35
- waitForInCallMs: number;
36
- audioInputCommand?: string[];
37
- audioOutputCommand?: string[];
38
- bargeInInputCommand?: string[];
39
- bargeInRmsThreshold: number;
40
- bargeInPeakThreshold: number;
41
- bargeInCooldownMs: number;
42
- audioBridgeCommand?: string[];
43
- audioBridgeHealthCommand?: string[];
44
- };
45
- chromeNode: {
46
- node?: string;
47
- };
48
- twilio: {
49
- defaultDialInNumber?: string;
50
- defaultPin?: string;
51
- defaultDtmfSequence?: string;
52
- };
53
- voiceCall: {
54
- enabled: boolean;
55
- gatewayUrl?: string;
56
- token?: string;
57
- requestTimeoutMs: number;
58
- dtmfDelayMs: number;
59
- postDtmfSpeechDelayMs: number;
60
- introMessage?: string;
61
- };
62
- realtime: {
63
- provider?: string;
64
- model?: string;
65
- instructions?: string;
66
- introMessage?: string;
67
- agentId?: string;
68
- toolPolicy: GoogleMeetToolPolicy;
69
- providers: Record<string, Record<string, unknown>>;
70
- };
71
- oauth: {
72
- clientId?: string;
73
- clientSecret?: string;
74
- refreshToken?: string;
75
- accessToken?: string;
76
- expiresAt?: number;
77
- };
78
- auth: {
79
- provider: "google-oauth";
80
- clientId?: string;
81
- clientSecret?: string;
82
- tokenPath?: string;
83
- };
84
- };
85
-
86
- export const DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND = [
87
- "sox",
88
- "-q",
89
- "-t",
90
- "coreaudio",
91
- "BlackHole 2ch",
92
- "-t",
93
- "raw",
94
- "-r",
95
- "24000",
96
- "-c",
97
- "1",
98
- "-e",
99
- "signed-integer",
100
- "-b",
101
- "16",
102
- "-L",
103
- "-",
104
- ] as const;
105
-
106
- export const DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND = [
107
- "sox",
108
- "-q",
109
- "-t",
110
- "raw",
111
- "-r",
112
- "24000",
113
- "-c",
114
- "1",
115
- "-e",
116
- "signed-integer",
117
- "-b",
118
- "16",
119
- "-L",
120
- "-",
121
- "-t",
122
- "coreaudio",
123
- "BlackHole 2ch",
124
- ] as const;
125
-
126
- const LEGACY_GOOGLE_MEET_AUDIO_INPUT_COMMAND = [
127
- "rec",
128
- "-q",
129
- "-t",
130
- "raw",
131
- "-r",
132
- "8000",
133
- "-c",
134
- "1",
135
- "-e",
136
- "mu-law",
137
- "-b",
138
- "8",
139
- "-",
140
- ] as const;
141
-
142
- const LEGACY_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND = [
143
- "play",
144
- "-q",
145
- "-t",
146
- "raw",
147
- "-r",
148
- "8000",
149
- "-c",
150
- "1",
151
- "-e",
152
- "mu-law",
153
- "-b",
154
- "8",
155
- "-",
156
- ] as const;
157
-
158
- const DEFAULT_GOOGLE_MEET_CHROME_AUDIO_FORMAT: GoogleMeetChromeAudioFormat = "pcm16-24khz";
159
- const DEFAULT_GOOGLE_MEET_BARGE_IN_RMS_THRESHOLD = 650;
160
- const DEFAULT_GOOGLE_MEET_BARGE_IN_PEAK_THRESHOLD = 2500;
161
- const DEFAULT_GOOGLE_MEET_BARGE_IN_COOLDOWN_MS = 900;
162
-
163
- const DEFAULT_GOOGLE_MEET_REALTIME_INSTRUCTIONS = `You are joining a private Google Meet as an OpenClaw agent. Keep spoken replies brief and natural. When a question needs deeper reasoning, current information, or tools, call ${REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME} before answering.`;
164
- const DEFAULT_GOOGLE_MEET_REALTIME_INTRO_MESSAGE = "Say exactly: I'm here and listening.";
165
-
166
- const DEFAULT_GOOGLE_MEET_CONFIG: GoogleMeetConfig = {
167
- enabled: true,
168
- defaults: {},
169
- preview: {
170
- enrollmentAcknowledged: false,
171
- },
172
- defaultTransport: "chrome",
173
- defaultMode: "realtime",
174
- chrome: {
175
- audioBackend: "blackhole-2ch",
176
- audioFormat: DEFAULT_GOOGLE_MEET_CHROME_AUDIO_FORMAT,
177
- launch: true,
178
- guestName: "OpenClaw Agent",
179
- reuseExistingTab: true,
180
- autoJoin: true,
181
- joinTimeoutMs: 30_000,
182
- waitForInCallMs: 20_000,
183
- audioInputCommand: [...DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND],
184
- audioOutputCommand: [...DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND],
185
- bargeInRmsThreshold: DEFAULT_GOOGLE_MEET_BARGE_IN_RMS_THRESHOLD,
186
- bargeInPeakThreshold: DEFAULT_GOOGLE_MEET_BARGE_IN_PEAK_THRESHOLD,
187
- bargeInCooldownMs: DEFAULT_GOOGLE_MEET_BARGE_IN_COOLDOWN_MS,
188
- },
189
- chromeNode: {},
190
- twilio: {},
191
- voiceCall: {
192
- enabled: true,
193
- requestTimeoutMs: 30_000,
194
- dtmfDelayMs: 2_500,
195
- postDtmfSpeechDelayMs: 5_000,
196
- },
197
- realtime: {
198
- provider: "openai",
199
- instructions: DEFAULT_GOOGLE_MEET_REALTIME_INSTRUCTIONS,
200
- introMessage: DEFAULT_GOOGLE_MEET_REALTIME_INTRO_MESSAGE,
201
- toolPolicy: "safe-read-only",
202
- providers: {},
203
- },
204
- oauth: {},
205
- auth: {
206
- provider: "google-oauth",
207
- },
208
- };
209
-
210
- const GOOGLE_MEET_CLIENT_ID_KEYS = ["OPENCLAW_GOOGLE_MEET_CLIENT_ID", "GOOGLE_MEET_CLIENT_ID"];
211
- const GOOGLE_MEET_CLIENT_SECRET_KEYS = [
212
- "OPENCLAW_GOOGLE_MEET_CLIENT_SECRET",
213
- "GOOGLE_MEET_CLIENT_SECRET",
214
- ] as const;
215
- const GOOGLE_MEET_REFRESH_TOKEN_KEYS = [
216
- "OPENCLAW_GOOGLE_MEET_REFRESH_TOKEN",
217
- "GOOGLE_MEET_REFRESH_TOKEN",
218
- ] as const;
219
- const GOOGLE_MEET_ACCESS_TOKEN_KEYS = [
220
- "OPENCLAW_GOOGLE_MEET_ACCESS_TOKEN",
221
- "GOOGLE_MEET_ACCESS_TOKEN",
222
- ] as const;
223
- const GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT_KEYS = [
224
- "OPENCLAW_GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT",
225
- "GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT",
226
- ] as const;
227
- const GOOGLE_MEET_DEFAULT_MEETING_KEYS = [
228
- "OPENCLAW_GOOGLE_MEET_DEFAULT_MEETING",
229
- "GOOGLE_MEET_DEFAULT_MEETING",
230
- ] as const;
231
- const GOOGLE_MEET_PREVIEW_ACK_KEYS = [
232
- "OPENCLAW_GOOGLE_MEET_PREVIEW_ACK",
233
- "GOOGLE_MEET_PREVIEW_ACK",
234
- ] as const;
235
-
236
- function asRecord(value: unknown): Record<string, unknown> {
237
- return value && typeof value === "object" && !Array.isArray(value)
238
- ? (value as Record<string, unknown>)
239
- : {};
240
- }
241
-
242
- function resolveBoolean(value: unknown, fallback: boolean): boolean {
243
- return typeof value === "boolean" ? value : fallback;
244
- }
245
-
246
- function resolveNumber(value: unknown, fallback: number): number {
247
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
248
- }
249
-
250
- function resolveOptionalNumber(value: unknown): number | undefined {
251
- if (typeof value === "number" && Number.isFinite(value)) {
252
- return value;
253
- }
254
- if (typeof value === "string" && value.trim()) {
255
- const parsed = Number(value);
256
- return Number.isFinite(parsed) ? parsed : undefined;
257
- }
258
- return undefined;
259
- }
260
-
261
- function readEnvString(env: NodeJS.ProcessEnv, keys: readonly string[]): string | undefined {
262
- for (const key of keys) {
263
- const value = normalizeOptionalString(env[key]);
264
- if (value) {
265
- return value;
266
- }
267
- }
268
- return undefined;
269
- }
270
-
271
- function readEnvBoolean(env: NodeJS.ProcessEnv, keys: readonly string[]): boolean | undefined {
272
- const normalized = normalizeOptionalLowercaseString(readEnvString(env, keys));
273
- if (!normalized) {
274
- return undefined;
275
- }
276
- if (["1", "true", "yes", "on"].includes(normalized)) {
277
- return true;
278
- }
279
- if (["0", "false", "no", "off"].includes(normalized)) {
280
- return false;
281
- }
282
- return undefined;
283
- }
284
-
285
- function readEnvNumber(env: NodeJS.ProcessEnv, keys: readonly string[]): number | undefined {
286
- return resolveOptionalNumber(readEnvString(env, keys));
287
- }
288
-
289
- function resolveStringArray(value: unknown): string[] | undefined {
290
- if (!Array.isArray(value)) {
291
- return undefined;
292
- }
293
- const normalized = value
294
- .map((entry) => normalizeOptionalString(entry))
295
- .filter((entry): entry is string => Boolean(entry));
296
- return normalized.length > 0 ? normalized : undefined;
297
- }
298
-
299
- function resolveProvidersConfig(value: unknown): Record<string, Record<string, unknown>> {
300
- const raw = asRecord(value);
301
- const providers: Record<string, Record<string, unknown>> = {};
302
- for (const [key, entry] of Object.entries(raw)) {
303
- const providerId = normalizeOptionalLowercaseString(key);
304
- if (!providerId) {
305
- continue;
306
- }
307
- providers[providerId] = asRecord(entry);
308
- }
309
- return providers;
310
- }
311
-
312
- function resolveTransport(value: unknown, fallback: GoogleMeetTransport): GoogleMeetTransport {
313
- const normalized = normalizeOptionalLowercaseString(value);
314
- return normalized === "chrome" || normalized === "chrome-node" || normalized === "twilio"
315
- ? normalized
316
- : fallback;
317
- }
318
-
319
- function resolveMode(value: unknown, fallback: GoogleMeetMode): GoogleMeetMode {
320
- const normalized = normalizeOptionalLowercaseString(value);
321
- return normalized === "realtime" || normalized === "transcribe" ? normalized : fallback;
322
- }
323
-
324
- function resolveChromeAudioFormat(value: unknown): GoogleMeetChromeAudioFormat | undefined {
325
- const normalized = normalizeOptionalString(value)?.toLowerCase().replaceAll("_", "-");
326
- switch (normalized) {
327
- case "pcm16-24khz":
328
- case "pcm16-24k":
329
- case "pcm24":
330
- case "pcm":
331
- return "pcm16-24khz";
332
- case "g711-ulaw-8khz":
333
- case "g711-ulaw-8k":
334
- case "g711-ulaw":
335
- case "mulaw":
336
- case "mu-law":
337
- return "g711-ulaw-8khz";
338
- default:
339
- return undefined;
340
- }
341
- }
342
-
343
- function defaultAudioInputCommand(format: GoogleMeetChromeAudioFormat): readonly string[] {
344
- return format === "g711-ulaw-8khz"
345
- ? LEGACY_GOOGLE_MEET_AUDIO_INPUT_COMMAND
346
- : DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND;
347
- }
348
-
349
- function defaultAudioOutputCommand(format: GoogleMeetChromeAudioFormat): readonly string[] {
350
- return format === "g711-ulaw-8khz"
351
- ? LEGACY_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND
352
- : DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND;
353
- }
354
-
355
- export function resolveGoogleMeetConfig(input: unknown): GoogleMeetConfig {
356
- return resolveGoogleMeetConfigWithEnv(input);
357
- }
358
-
359
- export function resolveGoogleMeetConfigWithEnv(
360
- input: unknown,
361
- env: NodeJS.ProcessEnv = process.env,
362
- ): GoogleMeetConfig {
363
- const raw = asRecord(input);
364
- const defaults = asRecord(raw.defaults);
365
- const preview = asRecord(raw.preview);
366
- const chrome = asRecord(raw.chrome);
367
- const configuredAudioInputCommand = resolveStringArray(chrome.audioInputCommand);
368
- const configuredAudioOutputCommand = resolveStringArray(chrome.audioOutputCommand);
369
- const hasCustomAudioCommand =
370
- configuredAudioInputCommand !== undefined || configuredAudioOutputCommand !== undefined;
371
- const audioFormat =
372
- resolveChromeAudioFormat(chrome.audioFormat) ??
373
- (hasCustomAudioCommand ? "g711-ulaw-8khz" : DEFAULT_GOOGLE_MEET_CONFIG.chrome.audioFormat);
374
- const chromeNode = asRecord(raw.chromeNode);
375
- const twilio = asRecord(raw.twilio);
376
- const voiceCall = asRecord(raw.voiceCall);
377
- const realtime = asRecord(raw.realtime);
378
- const oauth = asRecord(raw.oauth);
379
- const auth = asRecord(raw.auth);
380
-
381
- return {
382
- enabled: resolveBoolean(raw.enabled, DEFAULT_GOOGLE_MEET_CONFIG.enabled),
383
- defaults: {
384
- meeting:
385
- normalizeOptionalString(defaults.meeting) ??
386
- readEnvString(env, GOOGLE_MEET_DEFAULT_MEETING_KEYS),
387
- },
388
- preview: {
389
- enrollmentAcknowledged: resolveBoolean(
390
- preview.enrollmentAcknowledged,
391
- readEnvBoolean(env, GOOGLE_MEET_PREVIEW_ACK_KEYS) ??
392
- DEFAULT_GOOGLE_MEET_CONFIG.preview.enrollmentAcknowledged,
393
- ),
394
- },
395
- defaultTransport: resolveTransport(
396
- raw.defaultTransport,
397
- DEFAULT_GOOGLE_MEET_CONFIG.defaultTransport,
398
- ),
399
- defaultMode: resolveMode(raw.defaultMode, DEFAULT_GOOGLE_MEET_CONFIG.defaultMode),
400
- chrome: {
401
- audioBackend: "blackhole-2ch",
402
- audioFormat,
403
- launch: resolveBoolean(chrome.launch, DEFAULT_GOOGLE_MEET_CONFIG.chrome.launch),
404
- browserProfile: normalizeOptionalString(chrome.browserProfile),
405
- guestName:
406
- normalizeOptionalString(chrome.guestName) ?? DEFAULT_GOOGLE_MEET_CONFIG.chrome.guestName,
407
- reuseExistingTab: resolveBoolean(
408
- chrome.reuseExistingTab,
409
- DEFAULT_GOOGLE_MEET_CONFIG.chrome.reuseExistingTab,
410
- ),
411
- autoJoin: resolveBoolean(chrome.autoJoin, DEFAULT_GOOGLE_MEET_CONFIG.chrome.autoJoin),
412
- joinTimeoutMs: resolveNumber(
413
- chrome.joinTimeoutMs,
414
- DEFAULT_GOOGLE_MEET_CONFIG.chrome.joinTimeoutMs,
415
- ),
416
- waitForInCallMs: resolveNumber(
417
- chrome.waitForInCallMs,
418
- DEFAULT_GOOGLE_MEET_CONFIG.chrome.waitForInCallMs,
419
- ),
420
- audioInputCommand: configuredAudioInputCommand ?? [...defaultAudioInputCommand(audioFormat)],
421
- audioOutputCommand: configuredAudioOutputCommand ?? [
422
- ...defaultAudioOutputCommand(audioFormat),
423
- ],
424
- bargeInInputCommand: resolveStringArray(chrome.bargeInInputCommand),
425
- bargeInRmsThreshold: resolveNumber(
426
- chrome.bargeInRmsThreshold,
427
- DEFAULT_GOOGLE_MEET_CONFIG.chrome.bargeInRmsThreshold,
428
- ),
429
- bargeInPeakThreshold: resolveNumber(
430
- chrome.bargeInPeakThreshold,
431
- DEFAULT_GOOGLE_MEET_CONFIG.chrome.bargeInPeakThreshold,
432
- ),
433
- bargeInCooldownMs: resolveNumber(
434
- chrome.bargeInCooldownMs,
435
- DEFAULT_GOOGLE_MEET_CONFIG.chrome.bargeInCooldownMs,
436
- ),
437
- audioBridgeCommand: resolveStringArray(chrome.audioBridgeCommand),
438
- audioBridgeHealthCommand: resolveStringArray(chrome.audioBridgeHealthCommand),
439
- },
440
- chromeNode: {
441
- node: normalizeOptionalString(chromeNode.node),
442
- },
443
- twilio: {
444
- defaultDialInNumber: normalizeOptionalString(twilio.defaultDialInNumber),
445
- defaultPin: normalizeOptionalString(twilio.defaultPin),
446
- defaultDtmfSequence: normalizeOptionalString(twilio.defaultDtmfSequence),
447
- },
448
- voiceCall: {
449
- enabled: resolveBoolean(voiceCall.enabled, DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.enabled),
450
- gatewayUrl: normalizeOptionalString(voiceCall.gatewayUrl),
451
- token: normalizeOptionalString(voiceCall.token),
452
- requestTimeoutMs: resolveNumber(
453
- voiceCall.requestTimeoutMs,
454
- DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.requestTimeoutMs,
455
- ),
456
- dtmfDelayMs: resolveNumber(
457
- voiceCall.dtmfDelayMs,
458
- DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.dtmfDelayMs,
459
- ),
460
- postDtmfSpeechDelayMs: resolveNumber(
461
- voiceCall.postDtmfSpeechDelayMs,
462
- DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.postDtmfSpeechDelayMs,
463
- ),
464
- introMessage: normalizeOptionalString(voiceCall.introMessage),
465
- },
466
- realtime: {
467
- provider:
468
- normalizeOptionalString(realtime.provider) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.provider,
469
- model: normalizeOptionalString(realtime.model) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.model,
470
- instructions:
471
- normalizeOptionalString(realtime.instructions) ??
472
- DEFAULT_GOOGLE_MEET_CONFIG.realtime.instructions,
473
- introMessage:
474
- normalizeOptionalString(realtime.introMessage) ??
475
- DEFAULT_GOOGLE_MEET_CONFIG.realtime.introMessage,
476
- agentId: normalizeOptionalString(realtime.agentId),
477
- toolPolicy: resolveRealtimeVoiceAgentConsultToolPolicy(
478
- realtime.toolPolicy,
479
- DEFAULT_GOOGLE_MEET_CONFIG.realtime.toolPolicy,
480
- ),
481
- providers: resolveProvidersConfig(realtime.providers),
482
- },
483
- oauth: {
484
- clientId:
485
- normalizeOptionalString(oauth.clientId) ??
486
- normalizeOptionalString(auth.clientId) ??
487
- readEnvString(env, GOOGLE_MEET_CLIENT_ID_KEYS),
488
- clientSecret:
489
- normalizeOptionalString(oauth.clientSecret) ??
490
- normalizeOptionalString(auth.clientSecret) ??
491
- readEnvString(env, GOOGLE_MEET_CLIENT_SECRET_KEYS),
492
- refreshToken:
493
- normalizeOptionalString(oauth.refreshToken) ??
494
- readEnvString(env, GOOGLE_MEET_REFRESH_TOKEN_KEYS),
495
- accessToken:
496
- normalizeOptionalString(oauth.accessToken) ??
497
- readEnvString(env, GOOGLE_MEET_ACCESS_TOKEN_KEYS),
498
- expiresAt:
499
- resolveOptionalNumber(oauth.expiresAt) ??
500
- readEnvNumber(env, GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT_KEYS),
501
- },
502
- auth: {
503
- provider: "google-oauth",
504
- clientId: normalizeOptionalString(auth.clientId),
505
- clientSecret: normalizeOptionalString(auth.clientSecret),
506
- tokenPath: normalizeOptionalString(auth.tokenPath),
507
- },
508
- };
509
- }
package/src/create.ts DELETED
@@ -1,153 +0,0 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
2
- import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
3
- import type { GoogleMeetConfig, GoogleMeetMode, GoogleMeetTransport } from "./config.js";
4
- import {
5
- createGoogleMeetSpace,
6
- type GoogleMeetAccessType,
7
- type GoogleMeetEntryPointAccess,
8
- type GoogleMeetSpaceConfig,
9
- } from "./meet.js";
10
- import { resolveGoogleMeetAccessToken } from "./oauth.js";
11
- import type { GoogleMeetRuntime } from "./runtime.js";
12
- import { createMeetWithBrowserProxyOnNode } from "./transports/chrome-create.js";
13
-
14
- function normalizeTransport(value: unknown): GoogleMeetTransport | undefined {
15
- return value === "chrome" || value === "chrome-node" || value === "twilio" ? value : undefined;
16
- }
17
-
18
- function normalizeMode(value: unknown): GoogleMeetMode | undefined {
19
- return value === "realtime" || value === "transcribe" ? value : undefined;
20
- }
21
-
22
- function normalizeGoogleMeetAccessType(value: unknown): GoogleMeetAccessType | undefined {
23
- const normalized = normalizeOptionalString(value)?.toUpperCase().replaceAll("-", "_");
24
- return normalized === "OPEN" || normalized === "TRUSTED" || normalized === "RESTRICTED"
25
- ? normalized
26
- : undefined;
27
- }
28
-
29
- function normalizeGoogleMeetEntryPointAccess(
30
- value: unknown,
31
- ): GoogleMeetEntryPointAccess | undefined {
32
- const normalized = normalizeOptionalString(value)?.toUpperCase().replaceAll("-", "_");
33
- return normalized === "ALL" || normalized === "CREATOR_APP_ONLY" ? normalized : undefined;
34
- }
35
-
36
- export function resolveCreateSpaceConfig(
37
- raw: Record<string, unknown>,
38
- ): GoogleMeetSpaceConfig | undefined {
39
- const rawAccessType = normalizeOptionalString(raw.accessType);
40
- const rawEntryPointAccess = normalizeOptionalString(raw.entryPointAccess);
41
- const accessType = normalizeGoogleMeetAccessType(raw.accessType);
42
- const entryPointAccess = normalizeGoogleMeetEntryPointAccess(raw.entryPointAccess);
43
- if (rawAccessType !== undefined && !accessType) {
44
- throw new Error("Invalid Google Meet accessType. Expected OPEN, TRUSTED, or RESTRICTED.");
45
- }
46
- if (rawEntryPointAccess !== undefined && !entryPointAccess) {
47
- throw new Error("Invalid Google Meet entryPointAccess. Expected ALL or CREATOR_APP_ONLY.");
48
- }
49
- const config = {
50
- ...(accessType ? { accessType } : {}),
51
- ...(entryPointAccess ? { entryPointAccess } : {}),
52
- };
53
- return Object.keys(config).length > 0 ? config : undefined;
54
- }
55
-
56
- export function hasCreateSpaceConfigInput(raw: Record<string, unknown>): boolean {
57
- return (
58
- normalizeOptionalString(raw.accessType) !== undefined ||
59
- normalizeOptionalString(raw.entryPointAccess) !== undefined
60
- );
61
- }
62
-
63
- async function createSpaceFromParams(config: GoogleMeetConfig, raw: Record<string, unknown>) {
64
- const token = await resolveGoogleMeetAccessToken({
65
- clientId: normalizeOptionalString(raw.clientId) ?? config.oauth.clientId,
66
- clientSecret: normalizeOptionalString(raw.clientSecret) ?? config.oauth.clientSecret,
67
- refreshToken: normalizeOptionalString(raw.refreshToken) ?? config.oauth.refreshToken,
68
- accessToken: normalizeOptionalString(raw.accessToken) ?? config.oauth.accessToken,
69
- expiresAt: typeof raw.expiresAt === "number" ? raw.expiresAt : config.oauth.expiresAt,
70
- });
71
- const result = await createGoogleMeetSpace({
72
- accessToken: token.accessToken,
73
- config: resolveCreateSpaceConfig(raw),
74
- });
75
- return { source: "api" as const, token, ...result };
76
- }
77
-
78
- function hasGoogleMeetOAuth(config: GoogleMeetConfig, raw: Record<string, unknown>): boolean {
79
- return Boolean(
80
- normalizeOptionalString(raw.accessToken) ??
81
- normalizeOptionalString(raw.refreshToken) ??
82
- config.oauth.accessToken ??
83
- config.oauth.refreshToken,
84
- );
85
- }
86
-
87
- export async function createMeetFromParams(params: {
88
- config: GoogleMeetConfig;
89
- runtime: OpenClawPluginApi["runtime"];
90
- raw: Record<string, unknown>;
91
- }) {
92
- if (hasGoogleMeetOAuth(params.config, params.raw)) {
93
- const { token: _token, ...result } = await createSpaceFromParams(params.config, params.raw);
94
- return {
95
- ...result,
96
- joined: false,
97
- nextAction:
98
- "URL-only creation was requested. Call google_meet with action=join and url=meetingUri to enter the meeting.",
99
- };
100
- }
101
- if (hasCreateSpaceConfigInput(params.raw)) {
102
- throw new Error(
103
- "Google Meet access policy options require OAuth/API room creation. Configure Google Meet OAuth or remove accessType/entryPointAccess.",
104
- );
105
- }
106
- const browser = await createMeetWithBrowserProxyOnNode({
107
- runtime: params.runtime,
108
- config: params.config,
109
- });
110
- return {
111
- source: browser.source,
112
- meetingUri: browser.meetingUri,
113
- joined: false,
114
- nextAction:
115
- "URL-only creation was requested. Call google_meet with action=join and url=meetingUri to enter the meeting.",
116
- space: {
117
- name: `browser/${browser.meetingUri.split("/").pop()}`,
118
- meetingUri: browser.meetingUri,
119
- },
120
- browser: {
121
- nodeId: browser.nodeId,
122
- targetId: browser.targetId,
123
- browserUrl: browser.browserUrl,
124
- browserTitle: browser.browserTitle,
125
- notes: browser.notes,
126
- },
127
- };
128
- }
129
-
130
- export async function createAndJoinMeetFromParams(params: {
131
- config: GoogleMeetConfig;
132
- runtime: OpenClawPluginApi["runtime"];
133
- raw: Record<string, unknown>;
134
- ensureRuntime: () => Promise<GoogleMeetRuntime>;
135
- }) {
136
- const created = await createMeetFromParams(params);
137
- const rt = await params.ensureRuntime();
138
- const join = await rt.join({
139
- url: created.meetingUri,
140
- transport: normalizeTransport(params.raw.transport),
141
- mode: normalizeMode(params.raw.mode),
142
- dialInNumber: normalizeOptionalString(params.raw.dialInNumber),
143
- pin: normalizeOptionalString(params.raw.pin),
144
- dtmfSequence: normalizeOptionalString(params.raw.dtmfSequence),
145
- message: normalizeOptionalString(params.raw.message),
146
- });
147
- return {
148
- ...created,
149
- joined: true,
150
- nextAction: "Share meetingUri with participants; the OpenClaw agent has started the join flow.",
151
- join,
152
- };
153
- }