@openclaw/google-meet 2026.5.28 → 2026.5.31-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/dist/{chrome-create-B3zyjf5f.js → chrome-create-By4DuikO.js} +2 -1
- package/dist/{cli-Cc7dWaK8.js → cli-DURm3Y-K.js} +20 -11
- package/dist/config-Co24FcXM.js +467 -0
- package/dist/{create-OMWgK9on.js → create-BzkoPzD2.js} +2 -2
- package/dist/doctor-contract-api.js +1 -1
- package/dist/index.js +123 -460
- package/dist/{oauth-B3tz8JcD.js → oauth--J53aI66.js} +6 -5
- package/node_modules/typebox/build/type/script/mapping.d.mts +5 -2
- package/node_modules/typebox/build/type/script/mapping.mjs +15 -8
- package/node_modules/typebox/build/type/script/parser.d.mts +3 -1
- package/node_modules/typebox/build/type/script/parser.mjs +2 -1
- package/node_modules/typebox/package.json +29 -29
- package/npm-shrinkwrap.json +7 -7
- package/package.json +5 -5
- package/dist/calendar-IxWkFtfn.js +0 -136
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { a as isSameMeetUrlForReuse, c as resolveChromeNode, f as endGoogleMeetActiveConference, g as fetchLatestGoogleMeetConferenceRecord, h as fetchGoogleMeetSpace, i as callBrowserProxyOnNode, l as resolveChromeNodeInfo, m as fetchGoogleMeetAttendance, n as isGoogleMeetBrowserManualActionError, o as normalizeMeetUrlForReuse, p as fetchGoogleMeetArtifacts, r as asBrowserTabs, s as readBrowserTab, t as createMeetWithBrowserProxyOnNode, u as buildGoogleMeetPreflightReport } from "./chrome-create-
|
|
2
|
-
import { n as findGoogleMeetCalendarEvent, r as listGoogleMeetCalendarEvents, t as
|
|
1
|
+
import { a as isSameMeetUrlForReuse, c as resolveChromeNode, f as endGoogleMeetActiveConference, g as fetchLatestGoogleMeetConferenceRecord, h as fetchGoogleMeetSpace, i as callBrowserProxyOnNode, l as resolveChromeNodeInfo, m as fetchGoogleMeetAttendance, n as isGoogleMeetBrowserManualActionError, o as normalizeMeetUrlForReuse, p as fetchGoogleMeetArtifacts, r as asBrowserTabs, s as readBrowserTab, t as createMeetWithBrowserProxyOnNode, u as buildGoogleMeetPreflightReport } from "./chrome-create-By4DuikO.js";
|
|
2
|
+
import { a as buildGoogleMeetCalendarDayWindow, i as resolveGoogleMeetGatewayOperationTimeoutMs, n as DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND, o as findGoogleMeetCalendarEvent, r as resolveGoogleMeetConfig, s as listGoogleMeetCalendarEvents, t as DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND } from "./config-Co24FcXM.js";
|
|
3
3
|
import { optionalPositiveIntegerSchema, readPositiveIntegerParam } from "openclaw/plugin-sdk/channel-actions";
|
|
4
4
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
5
5
|
import { ErrorCodes, GatewayClient, callGatewayFromCli, errorShape, startGatewayClientWhenEventLoopReady } from "openclaw/plugin-sdk/gateway-runtime";
|
|
6
6
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
7
|
-
import { asRecord,
|
|
7
|
+
import { asRecord, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
8
8
|
import { Type } from "typebox";
|
|
9
9
|
import { isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
10
|
-
import {
|
|
10
|
+
import { addTimerTimeoutGraceMs } from "openclaw/plugin-sdk/number-runtime";
|
|
11
|
+
import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AUDIO_FORMAT_G711_ULAW_8KHZ, REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, buildRealtimeVoiceAgentConsultWorkingResponse, consultRealtimeVoiceAgent, convertPcmToMulaw8k, createRealtimeVoiceAgentTalkbackQueue, createRealtimeVoiceBridgeSession, createRealtimeVoiceOutputActivityTracker, createTalkSessionController, extendRealtimeVoiceOutputEchoSuppression, getRealtimeVoiceBridgeEventHealth, getRealtimeVoiceTranscriptHealth, isLikelyRealtimeVoiceAssistantEchoTranscript, mulawToPcm, recordRealtimeVoiceBridgeEvent, recordRealtimeVoiceTranscript, recordTalkObservabilityEvent, resamplePcm, resolveConfiguredRealtimeVoiceProvider, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceAgentConsultToolsAllow } from "openclaw/plugin-sdk/realtime-voice";
|
|
11
12
|
import { spawn, spawnSync } from "node:child_process";
|
|
12
13
|
import { randomUUID } from "node:crypto";
|
|
13
14
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -17,328 +18,6 @@ import os from "node:os";
|
|
|
17
18
|
import path from "node:path";
|
|
18
19
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
19
20
|
import { getRealtimeTranscriptionProvider, listRealtimeTranscriptionProviders } from "openclaw/plugin-sdk/realtime-transcription";
|
|
20
|
-
//#region extensions/google-meet/src/config.ts
|
|
21
|
-
const SOX_DEFAULT_BUFFER_BYTES = 8192;
|
|
22
|
-
const SOX_MIN_BUFFER_BYTES = 17;
|
|
23
|
-
const DEFAULT_GOOGLE_MEET_AUDIO_BUFFER_BYTES = SOX_DEFAULT_BUFFER_BYTES / 2;
|
|
24
|
-
const PLAIN_DECIMAL_NUMBER_RE = /^\d+(?:\.\d+)?$/;
|
|
25
|
-
function withSoxBuffer(command, bufferBytes) {
|
|
26
|
-
return [
|
|
27
|
-
command[0] ?? "sox",
|
|
28
|
-
"-q",
|
|
29
|
-
"--buffer",
|
|
30
|
-
String(bufferBytes),
|
|
31
|
-
...command.slice(2)
|
|
32
|
-
];
|
|
33
|
-
}
|
|
34
|
-
const DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND_BASE = [
|
|
35
|
-
"sox",
|
|
36
|
-
"-q",
|
|
37
|
-
"-t",
|
|
38
|
-
"coreaudio",
|
|
39
|
-
"BlackHole 2ch",
|
|
40
|
-
"-t",
|
|
41
|
-
"raw",
|
|
42
|
-
"-r",
|
|
43
|
-
"24000",
|
|
44
|
-
"-c",
|
|
45
|
-
"1",
|
|
46
|
-
"-e",
|
|
47
|
-
"signed-integer",
|
|
48
|
-
"-b",
|
|
49
|
-
"16",
|
|
50
|
-
"-L",
|
|
51
|
-
"-"
|
|
52
|
-
];
|
|
53
|
-
const DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND_BASE = [
|
|
54
|
-
"sox",
|
|
55
|
-
"-q",
|
|
56
|
-
"-t",
|
|
57
|
-
"raw",
|
|
58
|
-
"-r",
|
|
59
|
-
"24000",
|
|
60
|
-
"-c",
|
|
61
|
-
"1",
|
|
62
|
-
"-e",
|
|
63
|
-
"signed-integer",
|
|
64
|
-
"-b",
|
|
65
|
-
"16",
|
|
66
|
-
"-L",
|
|
67
|
-
"-",
|
|
68
|
-
"-t",
|
|
69
|
-
"coreaudio",
|
|
70
|
-
"BlackHole 2ch"
|
|
71
|
-
];
|
|
72
|
-
const LEGACY_GOOGLE_MEET_AUDIO_INPUT_COMMAND_BASE = [
|
|
73
|
-
"rec",
|
|
74
|
-
"-q",
|
|
75
|
-
"-t",
|
|
76
|
-
"raw",
|
|
77
|
-
"-r",
|
|
78
|
-
"8000",
|
|
79
|
-
"-c",
|
|
80
|
-
"1",
|
|
81
|
-
"-e",
|
|
82
|
-
"mu-law",
|
|
83
|
-
"-b",
|
|
84
|
-
"8",
|
|
85
|
-
"-"
|
|
86
|
-
];
|
|
87
|
-
const LEGACY_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND_BASE = [
|
|
88
|
-
"play",
|
|
89
|
-
"-q",
|
|
90
|
-
"-t",
|
|
91
|
-
"raw",
|
|
92
|
-
"-r",
|
|
93
|
-
"8000",
|
|
94
|
-
"-c",
|
|
95
|
-
"1",
|
|
96
|
-
"-e",
|
|
97
|
-
"mu-law",
|
|
98
|
-
"-b",
|
|
99
|
-
"8",
|
|
100
|
-
"-"
|
|
101
|
-
];
|
|
102
|
-
const DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND = withSoxBuffer(DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND_BASE, DEFAULT_GOOGLE_MEET_AUDIO_BUFFER_BYTES);
|
|
103
|
-
const DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND = withSoxBuffer(DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND_BASE, DEFAULT_GOOGLE_MEET_AUDIO_BUFFER_BYTES);
|
|
104
|
-
const DEFAULT_GOOGLE_MEET_CHROME_AUDIO_FORMAT = "pcm16-24khz";
|
|
105
|
-
const DEFAULT_GOOGLE_MEET_BARGE_IN_RMS_THRESHOLD = 650;
|
|
106
|
-
const DEFAULT_GOOGLE_MEET_BARGE_IN_PEAK_THRESHOLD = 2500;
|
|
107
|
-
const DEFAULT_GOOGLE_MEET_BARGE_IN_COOLDOWN_MS = 900;
|
|
108
|
-
const DEFAULT_GOOGLE_MEET_REALTIME_INSTRUCTIONS = `You are joining a private Google Meet as an OpenClaw voice transport. Keep spoken replies brief and natural. In agent mode, wait for OpenClaw consult results and speak them exactly. In bidi mode, answer directly and call ${REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME} for deeper reasoning, current information, or tools.`;
|
|
109
|
-
const DEFAULT_GOOGLE_MEET_REALTIME_INTRO_MESSAGE = "Say exactly: I'm here and listening.";
|
|
110
|
-
const DEFAULT_GOOGLE_MEET_CONFIG = {
|
|
111
|
-
enabled: true,
|
|
112
|
-
defaults: {},
|
|
113
|
-
preview: { enrollmentAcknowledged: false },
|
|
114
|
-
defaultTransport: "chrome",
|
|
115
|
-
defaultMode: "agent",
|
|
116
|
-
chrome: {
|
|
117
|
-
audioBackend: "blackhole-2ch",
|
|
118
|
-
audioFormat: DEFAULT_GOOGLE_MEET_CHROME_AUDIO_FORMAT,
|
|
119
|
-
audioBufferBytes: DEFAULT_GOOGLE_MEET_AUDIO_BUFFER_BYTES,
|
|
120
|
-
launch: true,
|
|
121
|
-
guestName: "OpenClaw Agent",
|
|
122
|
-
reuseExistingTab: true,
|
|
123
|
-
autoJoin: true,
|
|
124
|
-
joinTimeoutMs: 3e4,
|
|
125
|
-
waitForInCallMs: 2e4,
|
|
126
|
-
audioInputCommand: [...DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND],
|
|
127
|
-
audioOutputCommand: [...DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND],
|
|
128
|
-
bargeInRmsThreshold: DEFAULT_GOOGLE_MEET_BARGE_IN_RMS_THRESHOLD,
|
|
129
|
-
bargeInPeakThreshold: DEFAULT_GOOGLE_MEET_BARGE_IN_PEAK_THRESHOLD,
|
|
130
|
-
bargeInCooldownMs: DEFAULT_GOOGLE_MEET_BARGE_IN_COOLDOWN_MS
|
|
131
|
-
},
|
|
132
|
-
chromeNode: {},
|
|
133
|
-
twilio: {},
|
|
134
|
-
voiceCall: {
|
|
135
|
-
enabled: true,
|
|
136
|
-
requestTimeoutMs: 3e4,
|
|
137
|
-
dtmfDelayMs: 12e3,
|
|
138
|
-
postDtmfSpeechDelayMs: 5e3
|
|
139
|
-
},
|
|
140
|
-
realtime: {
|
|
141
|
-
strategy: "agent",
|
|
142
|
-
provider: "openai",
|
|
143
|
-
transcriptionProvider: "openai",
|
|
144
|
-
instructions: DEFAULT_GOOGLE_MEET_REALTIME_INSTRUCTIONS,
|
|
145
|
-
introMessage: DEFAULT_GOOGLE_MEET_REALTIME_INTRO_MESSAGE,
|
|
146
|
-
toolPolicy: "safe-read-only",
|
|
147
|
-
providers: {}
|
|
148
|
-
},
|
|
149
|
-
oauth: {},
|
|
150
|
-
auth: { provider: "google-oauth" }
|
|
151
|
-
};
|
|
152
|
-
const GOOGLE_MEET_CLIENT_ID_KEYS = ["OPENCLAW_GOOGLE_MEET_CLIENT_ID", "GOOGLE_MEET_CLIENT_ID"];
|
|
153
|
-
const GOOGLE_MEET_CLIENT_SECRET_KEYS = ["OPENCLAW_GOOGLE_MEET_CLIENT_SECRET", "GOOGLE_MEET_CLIENT_SECRET"];
|
|
154
|
-
const GOOGLE_MEET_REFRESH_TOKEN_KEYS = ["OPENCLAW_GOOGLE_MEET_REFRESH_TOKEN", "GOOGLE_MEET_REFRESH_TOKEN"];
|
|
155
|
-
const GOOGLE_MEET_ACCESS_TOKEN_KEYS = ["OPENCLAW_GOOGLE_MEET_ACCESS_TOKEN", "GOOGLE_MEET_ACCESS_TOKEN"];
|
|
156
|
-
const GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT_KEYS = ["OPENCLAW_GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT", "GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT"];
|
|
157
|
-
const GOOGLE_MEET_DEFAULT_MEETING_KEYS = ["OPENCLAW_GOOGLE_MEET_DEFAULT_MEETING", "GOOGLE_MEET_DEFAULT_MEETING"];
|
|
158
|
-
const GOOGLE_MEET_PREVIEW_ACK_KEYS = ["OPENCLAW_GOOGLE_MEET_PREVIEW_ACK", "GOOGLE_MEET_PREVIEW_ACK"];
|
|
159
|
-
function resolveBoolean(value, fallback) {
|
|
160
|
-
return typeof value === "boolean" ? value : fallback;
|
|
161
|
-
}
|
|
162
|
-
function resolveNumber(value, fallback) {
|
|
163
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
164
|
-
}
|
|
165
|
-
function resolveOptionalNumber(value) {
|
|
166
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
167
|
-
if (typeof value === "string" && value.trim()) {
|
|
168
|
-
const trimmed = value.trim();
|
|
169
|
-
const parsed = PLAIN_DECIMAL_NUMBER_RE.test(trimmed) ? Number(trimmed) : NaN;
|
|
170
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
function readEnvString(env, keys) {
|
|
174
|
-
for (const key of keys) {
|
|
175
|
-
const value = normalizeOptionalString(env[key]);
|
|
176
|
-
if (value) return value;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
function normalizeStringAllowEmpty(value) {
|
|
180
|
-
return typeof value === "string" ? value.trim() : void 0;
|
|
181
|
-
}
|
|
182
|
-
function readEnvBoolean(env, keys) {
|
|
183
|
-
const normalized = normalizeOptionalLowercaseString(readEnvString(env, keys));
|
|
184
|
-
if (!normalized) return;
|
|
185
|
-
if ([
|
|
186
|
-
"1",
|
|
187
|
-
"true",
|
|
188
|
-
"yes",
|
|
189
|
-
"on"
|
|
190
|
-
].includes(normalized)) return true;
|
|
191
|
-
if ([
|
|
192
|
-
"0",
|
|
193
|
-
"false",
|
|
194
|
-
"no",
|
|
195
|
-
"off"
|
|
196
|
-
].includes(normalized)) return false;
|
|
197
|
-
}
|
|
198
|
-
function readEnvNumber(env, keys) {
|
|
199
|
-
return resolveOptionalNumber(readEnvString(env, keys));
|
|
200
|
-
}
|
|
201
|
-
function resolveStringArray(value) {
|
|
202
|
-
return normalizeOptionalTrimmedStringList(value);
|
|
203
|
-
}
|
|
204
|
-
function resolveProvidersConfig(value) {
|
|
205
|
-
const raw = asRecord(value);
|
|
206
|
-
const providers = {};
|
|
207
|
-
for (const [key, entry] of Object.entries(raw)) {
|
|
208
|
-
const providerId = normalizeOptionalLowercaseString(key);
|
|
209
|
-
if (!providerId) continue;
|
|
210
|
-
providers[providerId] = asRecord(entry);
|
|
211
|
-
}
|
|
212
|
-
return providers;
|
|
213
|
-
}
|
|
214
|
-
function resolveTransport$1(value, fallback) {
|
|
215
|
-
const normalized = normalizeOptionalLowercaseString(value);
|
|
216
|
-
return normalized === "chrome" || normalized === "chrome-node" || normalized === "twilio" ? normalized : fallback;
|
|
217
|
-
}
|
|
218
|
-
function resolveMode$1(value, fallback) {
|
|
219
|
-
const normalized = normalizeOptionalLowercaseString(value);
|
|
220
|
-
if (normalized === "realtime") return "agent";
|
|
221
|
-
return normalized === "agent" || normalized === "bidi" || normalized === "transcribe" ? normalized : fallback;
|
|
222
|
-
}
|
|
223
|
-
function resolveRealtimeStrategy(value, fallback) {
|
|
224
|
-
const normalized = normalizeOptionalLowercaseString(value);
|
|
225
|
-
return normalized === "agent" || normalized === "bidi" ? normalized : fallback;
|
|
226
|
-
}
|
|
227
|
-
function resolveChromeAudioFormat(value) {
|
|
228
|
-
switch (normalizeOptionalString(value)?.toLowerCase().replaceAll("_", "-")) {
|
|
229
|
-
case "pcm16-24khz":
|
|
230
|
-
case "pcm16-24k":
|
|
231
|
-
case "pcm24":
|
|
232
|
-
case "pcm": return "pcm16-24khz";
|
|
233
|
-
case "g711-ulaw-8khz":
|
|
234
|
-
case "g711-ulaw-8k":
|
|
235
|
-
case "g711-ulaw":
|
|
236
|
-
case "mulaw":
|
|
237
|
-
case "mu-law": return "g711-ulaw-8khz";
|
|
238
|
-
default: return;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
function resolveAudioBufferBytes(value, fallback) {
|
|
242
|
-
const number = resolveNumber(value, fallback);
|
|
243
|
-
if (!Number.isFinite(number) || number <= 0) return fallback;
|
|
244
|
-
return Math.max(SOX_MIN_BUFFER_BYTES, Math.trunc(number));
|
|
245
|
-
}
|
|
246
|
-
function defaultAudioInputCommand(format, bufferBytes) {
|
|
247
|
-
return withSoxBuffer(format === "g711-ulaw-8khz" ? LEGACY_GOOGLE_MEET_AUDIO_INPUT_COMMAND_BASE : DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND_BASE, bufferBytes);
|
|
248
|
-
}
|
|
249
|
-
function defaultAudioOutputCommand(format, bufferBytes) {
|
|
250
|
-
return withSoxBuffer(format === "g711-ulaw-8khz" ? LEGACY_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND_BASE : DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND_BASE, bufferBytes);
|
|
251
|
-
}
|
|
252
|
-
function resolveGoogleMeetConfig(input) {
|
|
253
|
-
return resolveGoogleMeetConfigWithEnv(input);
|
|
254
|
-
}
|
|
255
|
-
function resolveGoogleMeetConfigWithEnv(input, env = process.env) {
|
|
256
|
-
const raw = asRecord(input);
|
|
257
|
-
const defaults = asRecord(raw.defaults);
|
|
258
|
-
const preview = asRecord(raw.preview);
|
|
259
|
-
const chrome = asRecord(raw.chrome);
|
|
260
|
-
const configuredAudioInputCommand = resolveStringArray(chrome.audioInputCommand);
|
|
261
|
-
const configuredAudioOutputCommand = resolveStringArray(chrome.audioOutputCommand);
|
|
262
|
-
const hasCustomAudioCommand = configuredAudioInputCommand !== void 0 || configuredAudioOutputCommand !== void 0;
|
|
263
|
-
const audioFormat = resolveChromeAudioFormat(chrome.audioFormat) ?? (hasCustomAudioCommand ? "g711-ulaw-8khz" : DEFAULT_GOOGLE_MEET_CONFIG.chrome.audioFormat);
|
|
264
|
-
const audioBufferBytes = resolveAudioBufferBytes(chrome.audioBufferBytes, DEFAULT_GOOGLE_MEET_CONFIG.chrome.audioBufferBytes);
|
|
265
|
-
const chromeNode = asRecord(raw.chromeNode);
|
|
266
|
-
const twilio = asRecord(raw.twilio);
|
|
267
|
-
const voiceCall = asRecord(raw.voiceCall);
|
|
268
|
-
const realtime = asRecord(raw.realtime);
|
|
269
|
-
const realtimeProvider = normalizeOptionalString(realtime.provider);
|
|
270
|
-
const resolvedRealtimeProvider = realtimeProvider ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.provider;
|
|
271
|
-
const oauth = asRecord(raw.oauth);
|
|
272
|
-
const auth = asRecord(raw.auth);
|
|
273
|
-
return {
|
|
274
|
-
enabled: resolveBoolean(raw.enabled, DEFAULT_GOOGLE_MEET_CONFIG.enabled),
|
|
275
|
-
defaults: { meeting: normalizeOptionalString(defaults.meeting) ?? readEnvString(env, GOOGLE_MEET_DEFAULT_MEETING_KEYS) },
|
|
276
|
-
preview: { enrollmentAcknowledged: resolveBoolean(preview.enrollmentAcknowledged, readEnvBoolean(env, GOOGLE_MEET_PREVIEW_ACK_KEYS) ?? DEFAULT_GOOGLE_MEET_CONFIG.preview.enrollmentAcknowledged) },
|
|
277
|
-
defaultTransport: resolveTransport$1(raw.defaultTransport, DEFAULT_GOOGLE_MEET_CONFIG.defaultTransport),
|
|
278
|
-
defaultMode: resolveMode$1(raw.defaultMode, DEFAULT_GOOGLE_MEET_CONFIG.defaultMode),
|
|
279
|
-
chrome: {
|
|
280
|
-
audioBackend: "blackhole-2ch",
|
|
281
|
-
audioFormat,
|
|
282
|
-
audioBufferBytes,
|
|
283
|
-
launch: resolveBoolean(chrome.launch, DEFAULT_GOOGLE_MEET_CONFIG.chrome.launch),
|
|
284
|
-
browserProfile: normalizeOptionalString(chrome.browserProfile),
|
|
285
|
-
guestName: normalizeOptionalString(chrome.guestName) ?? DEFAULT_GOOGLE_MEET_CONFIG.chrome.guestName,
|
|
286
|
-
reuseExistingTab: resolveBoolean(chrome.reuseExistingTab, DEFAULT_GOOGLE_MEET_CONFIG.chrome.reuseExistingTab),
|
|
287
|
-
autoJoin: resolveBoolean(chrome.autoJoin, DEFAULT_GOOGLE_MEET_CONFIG.chrome.autoJoin),
|
|
288
|
-
joinTimeoutMs: resolveNumber(chrome.joinTimeoutMs, DEFAULT_GOOGLE_MEET_CONFIG.chrome.joinTimeoutMs),
|
|
289
|
-
waitForInCallMs: resolveNumber(chrome.waitForInCallMs, DEFAULT_GOOGLE_MEET_CONFIG.chrome.waitForInCallMs),
|
|
290
|
-
audioInputCommand: configuredAudioInputCommand ?? defaultAudioInputCommand(audioFormat, audioBufferBytes),
|
|
291
|
-
audioOutputCommand: configuredAudioOutputCommand ?? defaultAudioOutputCommand(audioFormat, audioBufferBytes),
|
|
292
|
-
bargeInInputCommand: resolveStringArray(chrome.bargeInInputCommand),
|
|
293
|
-
bargeInRmsThreshold: resolveNumber(chrome.bargeInRmsThreshold, DEFAULT_GOOGLE_MEET_CONFIG.chrome.bargeInRmsThreshold),
|
|
294
|
-
bargeInPeakThreshold: resolveNumber(chrome.bargeInPeakThreshold, DEFAULT_GOOGLE_MEET_CONFIG.chrome.bargeInPeakThreshold),
|
|
295
|
-
bargeInCooldownMs: resolveNumber(chrome.bargeInCooldownMs, DEFAULT_GOOGLE_MEET_CONFIG.chrome.bargeInCooldownMs),
|
|
296
|
-
audioBridgeCommand: resolveStringArray(chrome.audioBridgeCommand),
|
|
297
|
-
audioBridgeHealthCommand: resolveStringArray(chrome.audioBridgeHealthCommand)
|
|
298
|
-
},
|
|
299
|
-
chromeNode: { node: normalizeOptionalString(chromeNode.node) },
|
|
300
|
-
twilio: {
|
|
301
|
-
defaultDialInNumber: normalizeOptionalString(twilio.defaultDialInNumber),
|
|
302
|
-
defaultPin: normalizeOptionalString(twilio.defaultPin),
|
|
303
|
-
defaultDtmfSequence: normalizeOptionalString(twilio.defaultDtmfSequence)
|
|
304
|
-
},
|
|
305
|
-
voiceCall: {
|
|
306
|
-
enabled: resolveBoolean(voiceCall.enabled, DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.enabled),
|
|
307
|
-
gatewayUrl: normalizeOptionalString(voiceCall.gatewayUrl),
|
|
308
|
-
token: normalizeOptionalString(voiceCall.token),
|
|
309
|
-
requestTimeoutMs: resolveNumber(voiceCall.requestTimeoutMs, DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.requestTimeoutMs),
|
|
310
|
-
dtmfDelayMs: resolveNumber(voiceCall.dtmfDelayMs, DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.dtmfDelayMs),
|
|
311
|
-
postDtmfSpeechDelayMs: resolveNumber(voiceCall.postDtmfSpeechDelayMs, DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.postDtmfSpeechDelayMs),
|
|
312
|
-
introMessage: normalizeOptionalString(voiceCall.introMessage)
|
|
313
|
-
},
|
|
314
|
-
realtime: {
|
|
315
|
-
strategy: resolveRealtimeStrategy(realtime.strategy, DEFAULT_GOOGLE_MEET_CONFIG.realtime.strategy),
|
|
316
|
-
provider: resolvedRealtimeProvider,
|
|
317
|
-
transcriptionProvider: normalizeOptionalString(realtime.transcriptionProvider) ?? (realtimeProvider && realtimeProvider !== "google" ? resolvedRealtimeProvider : DEFAULT_GOOGLE_MEET_CONFIG.realtime.transcriptionProvider),
|
|
318
|
-
voiceProvider: normalizeOptionalString(realtime.voiceProvider),
|
|
319
|
-
model: normalizeOptionalString(realtime.model) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.model,
|
|
320
|
-
instructions: normalizeOptionalString(realtime.instructions) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.instructions,
|
|
321
|
-
introMessage: normalizeStringAllowEmpty(realtime.introMessage) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.introMessage,
|
|
322
|
-
agentId: normalizeOptionalString(realtime.agentId),
|
|
323
|
-
toolPolicy: resolveRealtimeVoiceAgentConsultToolPolicy(realtime.toolPolicy, DEFAULT_GOOGLE_MEET_CONFIG.realtime.toolPolicy),
|
|
324
|
-
providers: resolveProvidersConfig(realtime.providers)
|
|
325
|
-
},
|
|
326
|
-
oauth: {
|
|
327
|
-
clientId: normalizeOptionalString(oauth.clientId) ?? normalizeOptionalString(auth.clientId) ?? readEnvString(env, GOOGLE_MEET_CLIENT_ID_KEYS),
|
|
328
|
-
clientSecret: normalizeOptionalString(oauth.clientSecret) ?? normalizeOptionalString(auth.clientSecret) ?? readEnvString(env, GOOGLE_MEET_CLIENT_SECRET_KEYS),
|
|
329
|
-
refreshToken: normalizeOptionalString(oauth.refreshToken) ?? readEnvString(env, GOOGLE_MEET_REFRESH_TOKEN_KEYS),
|
|
330
|
-
accessToken: normalizeOptionalString(oauth.accessToken) ?? readEnvString(env, GOOGLE_MEET_ACCESS_TOKEN_KEYS),
|
|
331
|
-
expiresAt: resolveOptionalNumber(oauth.expiresAt) ?? readEnvNumber(env, GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT_KEYS)
|
|
332
|
-
},
|
|
333
|
-
auth: {
|
|
334
|
-
provider: "google-oauth",
|
|
335
|
-
clientId: normalizeOptionalString(auth.clientId),
|
|
336
|
-
clientSecret: normalizeOptionalString(auth.clientSecret),
|
|
337
|
-
tokenPath: normalizeOptionalString(auth.tokenPath)
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
//#endregion
|
|
342
21
|
//#region extensions/google-meet/src/transports/chrome-audio-device.ts
|
|
343
22
|
const GOOGLE_MEET_SYSTEM_PROFILER_COMMAND = "/usr/sbin/system_profiler";
|
|
344
23
|
function outputMentionsBlackHole2ch(output) {
|
|
@@ -1076,7 +755,7 @@ function alawByteToLinear(value) {
|
|
|
1076
755
|
const sign = aLaw & 128;
|
|
1077
756
|
const exponent = (aLaw & 112) >> 4;
|
|
1078
757
|
const mantissa = aLaw & 15;
|
|
1079
|
-
|
|
758
|
+
const sample = exponent === 0 ? (mantissa << 4) + 8 : (mantissa << 4) + 264 << exponent - 1;
|
|
1080
759
|
return sign ? sample : -sample;
|
|
1081
760
|
}
|
|
1082
761
|
function resolveGoogleMeetRealtimeProvider(params) {
|
|
@@ -1204,7 +883,6 @@ async function startCommandAgentAudioBridge(params) {
|
|
|
1204
883
|
let lastSuppressedInputAt;
|
|
1205
884
|
let suppressInputUntil = 0;
|
|
1206
885
|
let lastOutputPlayableUntilMs = 0;
|
|
1207
|
-
let agentTalkback;
|
|
1208
886
|
let ttsQueue = Promise.resolve();
|
|
1209
887
|
const transcript = [];
|
|
1210
888
|
const resolved = resolveGoogleMeetRealtimeTranscriptionProvider({
|
|
@@ -1362,7 +1040,7 @@ async function startCommandAgentAudioBridge(params) {
|
|
|
1362
1040
|
params.logger.warn(`[google-meet] agent TTS failed: ${formatErrorMessage(error)}`);
|
|
1363
1041
|
});
|
|
1364
1042
|
};
|
|
1365
|
-
agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
|
|
1043
|
+
const agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
|
|
1366
1044
|
debounceMs: 900,
|
|
1367
1045
|
isStopped: () => stopped,
|
|
1368
1046
|
logger: params.logger,
|
|
@@ -1516,7 +1194,6 @@ async function startCommandRealtimeAudioBridge(params) {
|
|
|
1516
1194
|
let suppressInputUntil = 0;
|
|
1517
1195
|
let lastOutputPlayableUntilMs = 0;
|
|
1518
1196
|
let bargeInInputProcess;
|
|
1519
|
-
let agentTalkback;
|
|
1520
1197
|
const suppressInputForOutput = (audio) => {
|
|
1521
1198
|
const suppression = recordGoogleMeetOutputActivity({
|
|
1522
1199
|
tracker: outputActivity,
|
|
@@ -1695,7 +1372,7 @@ async function startCommandRealtimeAudioBridge(params) {
|
|
|
1695
1372
|
type: "session.started",
|
|
1696
1373
|
payload: { meetingSessionId: params.meetingSessionId }
|
|
1697
1374
|
});
|
|
1698
|
-
agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
|
|
1375
|
+
const agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
|
|
1699
1376
|
debounceMs: 900,
|
|
1700
1377
|
isStopped: () => stopped,
|
|
1701
1378
|
logger: params.logger,
|
|
@@ -1930,20 +1607,73 @@ function normalizeGoogleMeetTtsPromptText(text) {
|
|
|
1930
1607
|
if (sayExactly) return sayExactly.replace(/^["']|["']$/g, "").trim() || trimmed;
|
|
1931
1608
|
return trimmed;
|
|
1932
1609
|
}
|
|
1610
|
+
function startGoogleMeetNodeAudioInputLoop(params) {
|
|
1611
|
+
let lastInputAt;
|
|
1612
|
+
let lastInputBytes = 0;
|
|
1613
|
+
let suppressedInputBytes = 0;
|
|
1614
|
+
let lastSuppressedInputAt;
|
|
1615
|
+
let consecutiveInputErrors = 0;
|
|
1616
|
+
let lastInputError;
|
|
1617
|
+
(async () => {
|
|
1618
|
+
for (;;) {
|
|
1619
|
+
if (params.isStopped()) break;
|
|
1620
|
+
try {
|
|
1621
|
+
const raw = await params.runtime.nodes.invoke({
|
|
1622
|
+
nodeId: params.nodeId,
|
|
1623
|
+
command: "googlemeet.chrome",
|
|
1624
|
+
params: {
|
|
1625
|
+
action: "pullAudio",
|
|
1626
|
+
bridgeId: params.bridgeId,
|
|
1627
|
+
timeoutMs: 250
|
|
1628
|
+
},
|
|
1629
|
+
timeoutMs: 2e3
|
|
1630
|
+
});
|
|
1631
|
+
const result = asRecord$1(asRecord$1(raw).payload ?? raw);
|
|
1632
|
+
consecutiveInputErrors = 0;
|
|
1633
|
+
lastInputError = void 0;
|
|
1634
|
+
const base64 = readString(result.base64);
|
|
1635
|
+
if (base64) {
|
|
1636
|
+
const audio = Buffer.from(base64, "base64");
|
|
1637
|
+
if (params.isInputSuppressed()) {
|
|
1638
|
+
lastSuppressedInputAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1639
|
+
suppressedInputBytes += audio.byteLength;
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
lastInputAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1643
|
+
lastInputBytes += audio.byteLength;
|
|
1644
|
+
params.onAudio(audio);
|
|
1645
|
+
}
|
|
1646
|
+
if (result.closed === true) await params.stop();
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
if (!params.isStopped()) {
|
|
1649
|
+
const message = formatErrorMessage(error);
|
|
1650
|
+
consecutiveInputErrors += 1;
|
|
1651
|
+
lastInputError = message;
|
|
1652
|
+
params.logger.warn(`[google-meet] ${params.logPrefix} audio input failed (${consecutiveInputErrors}/5): ${message}`);
|
|
1653
|
+
if (consecutiveInputErrors >= 5 || /unknown bridgeId|bridge is not open/i.test(message)) await params.stop();
|
|
1654
|
+
else await new Promise((resolve) => setTimeout(resolve, 250));
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
})();
|
|
1659
|
+
return { getHealth: () => ({
|
|
1660
|
+
audioInputActive: lastInputBytes > 0,
|
|
1661
|
+
lastInputAt,
|
|
1662
|
+
lastSuppressedInputAt,
|
|
1663
|
+
lastInputBytes,
|
|
1664
|
+
suppressedInputBytes,
|
|
1665
|
+
consecutiveInputErrors,
|
|
1666
|
+
lastInputError
|
|
1667
|
+
}) };
|
|
1668
|
+
}
|
|
1933
1669
|
async function startNodeAgentAudioBridge(params) {
|
|
1934
1670
|
let stopped = false;
|
|
1935
1671
|
let sttSession = null;
|
|
1936
1672
|
let realtimeReady = false;
|
|
1937
|
-
let lastInputAt;
|
|
1938
1673
|
let lastOutputAt;
|
|
1939
|
-
let lastInputBytes = 0;
|
|
1940
1674
|
const outputActivity = createRealtimeVoiceOutputActivityTracker();
|
|
1941
|
-
let suppressedInputBytes = 0;
|
|
1942
|
-
let lastSuppressedInputAt;
|
|
1943
1675
|
let suppressInputUntil = 0;
|
|
1944
1676
|
let lastOutputPlayableUntilMs = 0;
|
|
1945
|
-
let consecutiveInputErrors = 0;
|
|
1946
|
-
let lastInputError;
|
|
1947
1677
|
const resolved = resolveGoogleMeetRealtimeTranscriptionProvider({
|
|
1948
1678
|
config: params.config,
|
|
1949
1679
|
fullConfig: params.fullConfig,
|
|
@@ -1955,7 +1685,6 @@ async function startNodeAgentAudioBridge(params) {
|
|
|
1955
1685
|
audioFormat: params.config.chrome.audioFormat
|
|
1956
1686
|
}));
|
|
1957
1687
|
const transcript = [];
|
|
1958
|
-
let agentTalkback;
|
|
1959
1688
|
let ttsQueue = Promise.resolve();
|
|
1960
1689
|
const stop = async () => {
|
|
1961
1690
|
if (stopped) return;
|
|
@@ -2021,7 +1750,7 @@ async function startNodeAgentAudioBridge(params) {
|
|
|
2021
1750
|
params.logger.warn(`[google-meet] node agent TTS failed: ${formatErrorMessage(error)}`);
|
|
2022
1751
|
});
|
|
2023
1752
|
};
|
|
2024
|
-
agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
|
|
1753
|
+
const agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
|
|
2025
1754
|
debounceMs: 900,
|
|
2026
1755
|
isStopped: () => stopped,
|
|
2027
1756
|
logger: params.logger,
|
|
@@ -2067,48 +1796,19 @@ async function startNodeAgentAudioBridge(params) {
|
|
|
2067
1796
|
});
|
|
2068
1797
|
await sttSession.connect();
|
|
2069
1798
|
realtimeReady = true;
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
},
|
|
2082
|
-
timeoutMs: 2e3
|
|
2083
|
-
});
|
|
2084
|
-
const result = asRecord$1(asRecord$1(raw).payload ?? raw);
|
|
2085
|
-
consecutiveInputErrors = 0;
|
|
2086
|
-
lastInputError = void 0;
|
|
2087
|
-
const base64 = readString(result.base64);
|
|
2088
|
-
if (base64) {
|
|
2089
|
-
const audio = Buffer.from(base64, "base64");
|
|
2090
|
-
if (Date.now() < suppressInputUntil) {
|
|
2091
|
-
lastSuppressedInputAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2092
|
-
suppressedInputBytes += audio.byteLength;
|
|
2093
|
-
continue;
|
|
2094
|
-
}
|
|
2095
|
-
lastInputAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2096
|
-
lastInputBytes += audio.byteLength;
|
|
2097
|
-
sttSession?.sendAudio(convertGoogleMeetBridgeAudioForStt(audio, params.config));
|
|
2098
|
-
}
|
|
2099
|
-
if (result.closed === true) await stop();
|
|
2100
|
-
} catch (error) {
|
|
2101
|
-
if (!stopped) {
|
|
2102
|
-
const message = formatErrorMessage(error);
|
|
2103
|
-
consecutiveInputErrors += 1;
|
|
2104
|
-
lastInputError = message;
|
|
2105
|
-
params.logger.warn(`[google-meet] node agent audio input failed (${consecutiveInputErrors}/5): ${message}`);
|
|
2106
|
-
if (consecutiveInputErrors >= 5 || /unknown bridgeId|bridge is not open/i.test(message)) await stop();
|
|
2107
|
-
else await new Promise((resolve) => setTimeout(resolve, 250));
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
1799
|
+
const audioInputLoop = startGoogleMeetNodeAudioInputLoop({
|
|
1800
|
+
runtime: params.runtime,
|
|
1801
|
+
nodeId: params.nodeId,
|
|
1802
|
+
bridgeId: params.bridgeId,
|
|
1803
|
+
logger: params.logger,
|
|
1804
|
+
logPrefix: "node agent",
|
|
1805
|
+
isStopped: () => stopped,
|
|
1806
|
+
stop,
|
|
1807
|
+
isInputSuppressed: () => Date.now() < suppressInputUntil,
|
|
1808
|
+
onAudio: (audio) => {
|
|
1809
|
+
sttSession?.sendAudio(convertGoogleMeetBridgeAudioForStt(audio, params.config));
|
|
2110
1810
|
}
|
|
2111
|
-
})
|
|
1811
|
+
});
|
|
2112
1812
|
return {
|
|
2113
1813
|
type: "node-command-pair",
|
|
2114
1814
|
providerId: resolved.provider.id,
|
|
@@ -2118,17 +1818,11 @@ async function startNodeAgentAudioBridge(params) {
|
|
|
2118
1818
|
getHealth: () => ({
|
|
2119
1819
|
providerConnected: sttSession?.isConnected() ?? false,
|
|
2120
1820
|
realtimeReady,
|
|
2121
|
-
|
|
1821
|
+
...audioInputLoop.getHealth(),
|
|
2122
1822
|
audioOutputActive: outputActivity.isActive(),
|
|
2123
|
-
lastInputAt,
|
|
2124
1823
|
lastOutputAt,
|
|
2125
|
-
lastSuppressedInputAt,
|
|
2126
|
-
lastInputBytes,
|
|
2127
1824
|
lastOutputBytes: outputActivity.snapshot().sinkAudioBytes,
|
|
2128
|
-
suppressedInputBytes,
|
|
2129
1825
|
...getGoogleMeetRealtimeTranscriptHealth(transcript),
|
|
2130
|
-
consecutiveInputErrors,
|
|
2131
|
-
lastInputError,
|
|
2132
1826
|
bridgeClosed: stopped
|
|
2133
1827
|
}),
|
|
2134
1828
|
stop
|
|
@@ -2138,17 +1832,11 @@ async function startNodeRealtimeAudioBridge(params) {
|
|
|
2138
1832
|
let stopped = false;
|
|
2139
1833
|
let bridge = null;
|
|
2140
1834
|
let realtimeReady = false;
|
|
2141
|
-
let lastInputAt;
|
|
2142
1835
|
let lastOutputAt;
|
|
2143
1836
|
let lastClearAt;
|
|
2144
|
-
let lastInputBytes = 0;
|
|
2145
1837
|
const outputActivity = createRealtimeVoiceOutputActivityTracker();
|
|
2146
|
-
let suppressedInputBytes = 0;
|
|
2147
|
-
let lastSuppressedInputAt;
|
|
2148
1838
|
let suppressInputUntil = 0;
|
|
2149
1839
|
let lastOutputPlayableUntilMs = 0;
|
|
2150
|
-
let consecutiveInputErrors = 0;
|
|
2151
|
-
let lastInputError;
|
|
2152
1840
|
let clearCount = 0;
|
|
2153
1841
|
const resolved = resolveGoogleMeetRealtimeProvider({
|
|
2154
1842
|
config: params.config,
|
|
@@ -2208,8 +1896,7 @@ async function startNodeRealtimeAudioBridge(params) {
|
|
|
2208
1896
|
fallbackModel: params.config.realtime.model,
|
|
2209
1897
|
audioFormat: params.config.chrome.audioFormat
|
|
2210
1898
|
}));
|
|
2211
|
-
|
|
2212
|
-
agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
|
|
1899
|
+
const agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
|
|
2213
1900
|
debounceMs: 900,
|
|
2214
1901
|
isStopped: () => stopped,
|
|
2215
1902
|
logger: params.logger,
|
|
@@ -2442,53 +2129,24 @@ async function startNodeRealtimeAudioBridge(params) {
|
|
|
2442
2129
|
}
|
|
2443
2130
|
});
|
|
2444
2131
|
await bridge.connect();
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
lastInputError = void 0;
|
|
2462
|
-
const base64 = readString(result.base64);
|
|
2463
|
-
if (base64) {
|
|
2464
|
-
const audio = Buffer.from(base64, "base64");
|
|
2465
|
-
if (Date.now() < suppressInputUntil) {
|
|
2466
|
-
lastSuppressedInputAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2467
|
-
suppressedInputBytes += audio.byteLength;
|
|
2468
|
-
continue;
|
|
2469
|
-
}
|
|
2470
|
-
lastInputAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2471
|
-
lastInputBytes += audio.byteLength;
|
|
2472
|
-
emitTalkEvent({
|
|
2473
|
-
type: "input.audio.delta",
|
|
2474
|
-
turnId: ensureTalkTurn(),
|
|
2475
|
-
payload: { byteLength: audio.byteLength }
|
|
2476
|
-
});
|
|
2477
|
-
bridge?.sendAudio(audio);
|
|
2478
|
-
}
|
|
2479
|
-
if (result.closed === true) await stop();
|
|
2480
|
-
} catch (error) {
|
|
2481
|
-
if (!stopped) {
|
|
2482
|
-
const message = formatErrorMessage(error);
|
|
2483
|
-
consecutiveInputErrors += 1;
|
|
2484
|
-
lastInputError = message;
|
|
2485
|
-
params.logger.warn(`[google-meet] node audio input failed (${consecutiveInputErrors}/5): ${message}`);
|
|
2486
|
-
if (consecutiveInputErrors >= 5 || /unknown bridgeId|bridge is not open/i.test(message)) await stop();
|
|
2487
|
-
else await new Promise((resolve) => setTimeout(resolve, 250));
|
|
2488
|
-
}
|
|
2489
|
-
}
|
|
2132
|
+
const audioInputLoop = startGoogleMeetNodeAudioInputLoop({
|
|
2133
|
+
runtime: params.runtime,
|
|
2134
|
+
nodeId: params.nodeId,
|
|
2135
|
+
bridgeId: params.bridgeId,
|
|
2136
|
+
logger: params.logger,
|
|
2137
|
+
logPrefix: "node",
|
|
2138
|
+
isStopped: () => stopped,
|
|
2139
|
+
stop,
|
|
2140
|
+
isInputSuppressed: () => Date.now() < suppressInputUntil,
|
|
2141
|
+
onAudio: (audio) => {
|
|
2142
|
+
emitTalkEvent({
|
|
2143
|
+
type: "input.audio.delta",
|
|
2144
|
+
turnId: ensureTalkTurn(),
|
|
2145
|
+
payload: { byteLength: audio.byteLength }
|
|
2146
|
+
});
|
|
2147
|
+
bridge?.sendAudio(audio);
|
|
2490
2148
|
}
|
|
2491
|
-
})
|
|
2149
|
+
});
|
|
2492
2150
|
return {
|
|
2493
2151
|
type: "node-command-pair",
|
|
2494
2152
|
providerId: resolved.provider.id,
|
|
@@ -2500,20 +2158,14 @@ async function startNodeRealtimeAudioBridge(params) {
|
|
|
2500
2158
|
getHealth: () => ({
|
|
2501
2159
|
providerConnected: bridge?.bridge.isConnected() ?? false,
|
|
2502
2160
|
realtimeReady,
|
|
2503
|
-
|
|
2161
|
+
...audioInputLoop.getHealth(),
|
|
2504
2162
|
audioOutputActive: outputActivity.isActive(),
|
|
2505
|
-
lastInputAt,
|
|
2506
2163
|
lastOutputAt,
|
|
2507
|
-
lastSuppressedInputAt,
|
|
2508
2164
|
lastClearAt,
|
|
2509
|
-
lastInputBytes,
|
|
2510
2165
|
lastOutputBytes: outputActivity.snapshot().sinkAudioBytes,
|
|
2511
|
-
suppressedInputBytes,
|
|
2512
2166
|
...getGoogleMeetRealtimeTranscriptHealth(transcript),
|
|
2513
2167
|
...getGoogleMeetRealtimeEventHealth(realtimeEvents),
|
|
2514
2168
|
recentTalkEvents: summarizeGoogleMeetTalkEvents(recentTalkEvents),
|
|
2515
|
-
consecutiveInputErrors,
|
|
2516
|
-
lastInputError,
|
|
2517
2169
|
clearCount,
|
|
2518
2170
|
bridgeClosed: stopped
|
|
2519
2171
|
}),
|
|
@@ -2650,7 +2302,7 @@ function parseMeetBrowserStatus(result) {
|
|
|
2650
2302
|
async function callLocalBrowserRequest(params) {
|
|
2651
2303
|
return await chromeTransportDeps.callGatewayFromCli("browser.request", {
|
|
2652
2304
|
json: true,
|
|
2653
|
-
timeout: String(params.timeoutMs
|
|
2305
|
+
timeout: String(resolveBrowserGatewayTimeoutMs(params.timeoutMs))
|
|
2654
2306
|
}, {
|
|
2655
2307
|
method: params.method,
|
|
2656
2308
|
path: params.path,
|
|
@@ -2658,6 +2310,9 @@ async function callLocalBrowserRequest(params) {
|
|
|
2658
2310
|
timeoutMs: params.timeoutMs
|
|
2659
2311
|
}, { progress: false });
|
|
2660
2312
|
}
|
|
2313
|
+
function resolveBrowserGatewayTimeoutMs(timeoutMs) {
|
|
2314
|
+
return addTimerTimeoutGraceMs(timeoutMs) ?? 1;
|
|
2315
|
+
}
|
|
2661
2316
|
function mergeBrowserNotes(browser, notes) {
|
|
2662
2317
|
if (!browser || notes.length === 0) return browser;
|
|
2663
2318
|
return {
|
|
@@ -3214,7 +2869,7 @@ async function launchChromeMeetOnNode(params) {
|
|
|
3214
2869
|
audioBridgeCommand: params.config.chrome.audioBridgeCommand,
|
|
3215
2870
|
audioBridgeHealthCommand: params.config.chrome.audioBridgeHealthCommand
|
|
3216
2871
|
},
|
|
3217
|
-
timeoutMs: params.config.chrome.joinTimeoutMs
|
|
2872
|
+
timeoutMs: addTimerTimeoutGraceMs(params.config.chrome.joinTimeoutMs) ?? 1
|
|
3218
2873
|
}));
|
|
3219
2874
|
if (result.audioBridge?.type === "node-command-pair") {
|
|
3220
2875
|
if (!result.bridgeId) throw new Error("Google Meet node did not return an audio bridge id.");
|
|
@@ -4082,6 +3737,16 @@ var GoogleMeetRuntime = class {
|
|
|
4082
3737
|
};
|
|
4083
3738
|
//#endregion
|
|
4084
3739
|
//#region extensions/google-meet/index.ts
|
|
3740
|
+
let googleMeetCreateModulePromise = null;
|
|
3741
|
+
let googleMeetCliModulePromise = null;
|
|
3742
|
+
const loadGoogleMeetCreateModule = async () => {
|
|
3743
|
+
googleMeetCreateModulePromise ??= import("./create-BzkoPzD2.js");
|
|
3744
|
+
return await googleMeetCreateModulePromise;
|
|
3745
|
+
};
|
|
3746
|
+
const loadGoogleMeetCliModule = async () => {
|
|
3747
|
+
googleMeetCliModulePromise ??= import("./cli-DURm3Y-K.js");
|
|
3748
|
+
return await googleMeetCliModulePromise;
|
|
3749
|
+
};
|
|
4085
3750
|
const googleMeetConfigSchema = {
|
|
4086
3751
|
parse(value) {
|
|
4087
3752
|
return resolveGoogleMeetConfig(value);
|
|
@@ -4404,7 +4069,8 @@ const testing = {
|
|
|
4404
4069
|
setPlatformForTests(next) {
|
|
4405
4070
|
googleMeetToolDeps.platform = next ?? (() => process.platform);
|
|
4406
4071
|
},
|
|
4407
|
-
isGoogleMeetAgentToolActionUnsupportedOnHost
|
|
4072
|
+
isGoogleMeetAgentToolActionUnsupportedOnHost,
|
|
4073
|
+
resolveGoogleMeetGatewayOperationTimeoutMs
|
|
4408
4074
|
};
|
|
4409
4075
|
function googleMeetGatewayMethodForToolAction(action) {
|
|
4410
4076
|
switch (action) {
|
|
@@ -4428,9 +4094,6 @@ function assertGoogleMeetAgentToolActionSupported(params) {
|
|
|
4428
4094
|
if (!isGoogleMeetAgentToolActionUnsupportedOnHost(params)) return;
|
|
4429
4095
|
throw new Error("Google Meet local Chrome talk-back audio is macOS-only. On this host, use mode: transcribe, transport: twilio, or transport: chrome-node backed by a macOS node.");
|
|
4430
4096
|
}
|
|
4431
|
-
function resolveGoogleMeetToolGatewayTimeoutMs(config) {
|
|
4432
|
-
return Math.max(6e4, config.chrome.joinTimeoutMs + 3e4, config.voiceCall.requestTimeoutMs + 1e4);
|
|
4433
|
-
}
|
|
4434
4097
|
function readGatewayErrorDetails(err) {
|
|
4435
4098
|
if (!err || typeof err !== "object" || !("details" in err)) return;
|
|
4436
4099
|
return err.details;
|
|
@@ -4439,7 +4102,7 @@ async function callGoogleMeetGatewayFromTool(params) {
|
|
|
4439
4102
|
try {
|
|
4440
4103
|
return await googleMeetToolDeps.callGatewayFromCli(googleMeetGatewayMethodForToolAction(params.action), {
|
|
4441
4104
|
json: true,
|
|
4442
|
-
timeout: String(
|
|
4105
|
+
timeout: String(resolveGoogleMeetGatewayOperationTimeoutMs(params.config))
|
|
4443
4106
|
}, params.raw, { progress: false });
|
|
4444
4107
|
} catch (err) {
|
|
4445
4108
|
const details = readGatewayErrorDetails(err);
|
|
@@ -4448,13 +4111,13 @@ async function callGoogleMeetGatewayFromTool(params) {
|
|
|
4448
4111
|
}
|
|
4449
4112
|
}
|
|
4450
4113
|
async function createMeetFromParams(params) {
|
|
4451
|
-
return (await
|
|
4114
|
+
return (await loadGoogleMeetCreateModule()).createMeetFromParams(params);
|
|
4452
4115
|
}
|
|
4453
4116
|
async function createAndJoinMeetFromParams(params) {
|
|
4454
|
-
return (await
|
|
4117
|
+
return (await loadGoogleMeetCreateModule()).createAndJoinMeetFromParams(params);
|
|
4455
4118
|
}
|
|
4456
4119
|
async function resolveGoogleMeetTokenFromParams(config, raw) {
|
|
4457
|
-
const { resolveGoogleMeetAccessToken } = await import("./oauth
|
|
4120
|
+
const { resolveGoogleMeetAccessToken } = await import("./oauth--J53aI66.js");
|
|
4458
4121
|
return resolveGoogleMeetAccessToken({
|
|
4459
4122
|
clientId: normalizeOptionalString(raw.clientId) ?? config.oauth.clientId,
|
|
4460
4123
|
clientSecret: normalizeOptionalString(raw.clientSecret) ?? config.oauth.clientSecret,
|
|
@@ -4543,7 +4206,7 @@ async function exportGoogleMeetBundleFromParams(config, raw) {
|
|
|
4543
4206
|
lateAfterMinutes: resolved.lateAfterMinutes,
|
|
4544
4207
|
earlyBeforeMinutes: resolved.earlyBeforeMinutes
|
|
4545
4208
|
})]);
|
|
4546
|
-
const { buildGoogleMeetExportManifest, googleMeetExportFileNames, writeMeetExportBundle } = await
|
|
4209
|
+
const { buildGoogleMeetExportManifest, googleMeetExportFileNames, writeMeetExportBundle } = await loadGoogleMeetCliModule();
|
|
4547
4210
|
const calendarId = normalizeOptionalString(raw.calendarId);
|
|
4548
4211
|
const request = {
|
|
4549
4212
|
...resolved.meeting ? { meeting: resolved.meeting } : {},
|
|
@@ -4959,7 +4622,7 @@ var google_meet_default = definePluginEntry({
|
|
|
4959
4622
|
handle: handleGoogleMeetNodeHostCommand
|
|
4960
4623
|
});
|
|
4961
4624
|
api.registerCli(async ({ program }) => {
|
|
4962
|
-
const { registerGoogleMeetCli } = await
|
|
4625
|
+
const { registerGoogleMeetCli } = await loadGoogleMeetCliModule();
|
|
4963
4626
|
registerGoogleMeetCli({
|
|
4964
4627
|
program,
|
|
4965
4628
|
config,
|