@openclaw/google-meet 2026.5.2 → 2026.5.3-beta.2
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/calendar-6EQiwLUb.js +126 -0
- package/dist/chrome-create-B0wV2zaj.js +963 -0
- package/dist/cli-CSyT9_N6.js +1377 -0
- package/dist/create-0ye_2zVk.js +106 -0
- package/dist/index.js +3509 -0
- package/dist/oauth-BJwzuzT-.js +141 -0
- package/package.json +13 -6
- package/google-meet.live.test.ts +0 -85
- package/index.create.test.ts +0 -595
- package/index.test.ts +0 -3616
- package/index.ts +0 -1170
- package/node-host.test.ts +0 -222
- package/src/agent-consult.ts +0 -70
- package/src/calendar.ts +0 -243
- package/src/cli.test.ts +0 -1013
- package/src/cli.ts +0 -2136
- package/src/config.ts +0 -509
- package/src/create.ts +0 -153
- package/src/drive.ts +0 -72
- package/src/google-api-errors.ts +0 -20
- package/src/meet.ts +0 -1024
- package/src/node-host.ts +0 -498
- package/src/oauth.test.ts +0 -71
- package/src/oauth.ts +0 -229
- package/src/realtime-node.ts +0 -271
- package/src/realtime.ts +0 -423
- package/src/runtime.ts +0 -804
- package/src/setup.ts +0 -276
- package/src/test-support/plugin-harness.ts +0 -225
- package/src/transports/chrome-browser-proxy.ts +0 -198
- package/src/transports/chrome-create.ts +0 -367
- package/src/transports/chrome.ts +0 -925
- package/src/transports/twilio.ts +0 -46
- package/src/transports/types.ts +0 -113
- package/src/voice-call-gateway.test.ts +0 -79
- package/src/voice-call-gateway.ts +0 -213
- package/tsconfig.json +0 -16
package/src/realtime.ts
DELETED
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import type { Writable } from "node:stream";
|
|
3
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
4
|
-
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
5
|
-
import type { PluginRuntime, RuntimeLogger } from "openclaw/plugin-sdk/plugin-runtime";
|
|
6
|
-
import {
|
|
7
|
-
createRealtimeVoiceBridgeSession,
|
|
8
|
-
REALTIME_VOICE_AUDIO_FORMAT_G711_ULAW_8KHZ,
|
|
9
|
-
REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ,
|
|
10
|
-
resolveConfiguredRealtimeVoiceProvider,
|
|
11
|
-
type RealtimeVoiceBridgeSession,
|
|
12
|
-
type RealtimeVoiceProviderConfig,
|
|
13
|
-
type RealtimeVoiceProviderPlugin,
|
|
14
|
-
} from "openclaw/plugin-sdk/realtime-voice";
|
|
15
|
-
import {
|
|
16
|
-
consultOpenClawAgentForGoogleMeet,
|
|
17
|
-
GOOGLE_MEET_AGENT_CONSULT_TOOL_NAME,
|
|
18
|
-
resolveGoogleMeetRealtimeTools,
|
|
19
|
-
submitGoogleMeetConsultWorkingResponse,
|
|
20
|
-
} from "./agent-consult.js";
|
|
21
|
-
import type { GoogleMeetConfig } from "./config.js";
|
|
22
|
-
import type { GoogleMeetChromeHealth } from "./transports/types.js";
|
|
23
|
-
|
|
24
|
-
type BridgeProcess = {
|
|
25
|
-
pid?: number;
|
|
26
|
-
killed?: boolean;
|
|
27
|
-
stdin?: Writable | null;
|
|
28
|
-
stdout?: { on(event: "data", listener: (chunk: Buffer | string) => void): unknown } | null;
|
|
29
|
-
stderr?: { on(event: "data", listener: (chunk: Buffer | string) => void): unknown } | null;
|
|
30
|
-
kill(signal?: NodeJS.Signals): boolean;
|
|
31
|
-
on(
|
|
32
|
-
event: "exit",
|
|
33
|
-
listener: (code: number | null, signal: NodeJS.Signals | null) => void,
|
|
34
|
-
): unknown;
|
|
35
|
-
on(event: "error", listener: (error: Error) => void): unknown;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
type SpawnFn = (
|
|
39
|
-
command: string,
|
|
40
|
-
args: string[],
|
|
41
|
-
options: { stdio: ["pipe" | "ignore", "pipe" | "ignore", "pipe" | "ignore"] },
|
|
42
|
-
) => BridgeProcess;
|
|
43
|
-
|
|
44
|
-
export type ChromeRealtimeAudioBridgeHandle = {
|
|
45
|
-
providerId: string;
|
|
46
|
-
inputCommand: string[];
|
|
47
|
-
outputCommand: string[];
|
|
48
|
-
speak: (instructions?: string) => void;
|
|
49
|
-
getHealth: () => GoogleMeetChromeHealth;
|
|
50
|
-
stop: () => Promise<void>;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
type ResolvedRealtimeProvider = {
|
|
54
|
-
provider: RealtimeVoiceProviderPlugin;
|
|
55
|
-
providerConfig: RealtimeVoiceProviderConfig;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
function splitCommand(argv: string[]): { command: string; args: string[] } {
|
|
59
|
-
const [command, ...args] = argv;
|
|
60
|
-
if (!command) {
|
|
61
|
-
throw new Error("audio bridge command must not be empty");
|
|
62
|
-
}
|
|
63
|
-
return { command, args };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function readPcm16Stats(audio: Buffer): { rms: number; peak: number } {
|
|
67
|
-
let sumSquares = 0;
|
|
68
|
-
let peak = 0;
|
|
69
|
-
let samples = 0;
|
|
70
|
-
for (let offset = 0; offset + 1 < audio.byteLength; offset += 2) {
|
|
71
|
-
const sample = audio.readInt16LE(offset);
|
|
72
|
-
const abs = Math.abs(sample);
|
|
73
|
-
peak = Math.max(peak, abs);
|
|
74
|
-
sumSquares += sample * sample;
|
|
75
|
-
samples += 1;
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
rms: samples > 0 ? Math.sqrt(sumSquares / samples) : 0,
|
|
79
|
-
peak,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function resolveGoogleMeetRealtimeAudioFormat(config: GoogleMeetConfig) {
|
|
84
|
-
return config.chrome.audioFormat === "g711-ulaw-8khz"
|
|
85
|
-
? REALTIME_VOICE_AUDIO_FORMAT_G711_ULAW_8KHZ
|
|
86
|
-
: REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function resolveGoogleMeetRealtimeProvider(params: {
|
|
90
|
-
config: GoogleMeetConfig;
|
|
91
|
-
fullConfig: OpenClawConfig;
|
|
92
|
-
providers?: RealtimeVoiceProviderPlugin[];
|
|
93
|
-
}): ResolvedRealtimeProvider {
|
|
94
|
-
return resolveConfiguredRealtimeVoiceProvider({
|
|
95
|
-
configuredProviderId: params.config.realtime.provider,
|
|
96
|
-
providerConfigs: params.config.realtime.providers,
|
|
97
|
-
cfg: params.fullConfig,
|
|
98
|
-
providers: params.providers,
|
|
99
|
-
defaultModel: params.config.realtime.model,
|
|
100
|
-
noRegisteredProviderMessage: "No configured realtime voice provider registered",
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export async function startCommandRealtimeAudioBridge(params: {
|
|
105
|
-
config: GoogleMeetConfig;
|
|
106
|
-
fullConfig: OpenClawConfig;
|
|
107
|
-
runtime: PluginRuntime;
|
|
108
|
-
meetingSessionId: string;
|
|
109
|
-
inputCommand: string[];
|
|
110
|
-
outputCommand: string[];
|
|
111
|
-
logger: RuntimeLogger;
|
|
112
|
-
providers?: RealtimeVoiceProviderPlugin[];
|
|
113
|
-
spawn?: SpawnFn;
|
|
114
|
-
}): Promise<ChromeRealtimeAudioBridgeHandle> {
|
|
115
|
-
const input = splitCommand(params.inputCommand);
|
|
116
|
-
const output = splitCommand(params.outputCommand);
|
|
117
|
-
const spawnFn: SpawnFn =
|
|
118
|
-
params.spawn ??
|
|
119
|
-
((command, args, options) => spawn(command, args, options) as unknown as BridgeProcess);
|
|
120
|
-
const spawnOutputProcess = () =>
|
|
121
|
-
spawnFn(output.command, output.args, {
|
|
122
|
-
stdio: ["pipe", "ignore", "pipe"],
|
|
123
|
-
});
|
|
124
|
-
let outputProcess = spawnOutputProcess();
|
|
125
|
-
const inputProcess = spawnFn(input.command, input.args, {
|
|
126
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
127
|
-
});
|
|
128
|
-
let stopped = false;
|
|
129
|
-
let bridge: RealtimeVoiceBridgeSession | null = null;
|
|
130
|
-
let realtimeReady = false;
|
|
131
|
-
let lastInputAt: string | undefined;
|
|
132
|
-
let lastOutputAt: string | undefined;
|
|
133
|
-
let lastInputBytes = 0;
|
|
134
|
-
let lastOutputBytes = 0;
|
|
135
|
-
let lastClearAt: string | undefined;
|
|
136
|
-
let clearCount = 0;
|
|
137
|
-
let suppressedInputBytes = 0;
|
|
138
|
-
let lastSuppressedInputAt: string | undefined;
|
|
139
|
-
let suppressInputUntil = 0;
|
|
140
|
-
let lastOutputAtMs = 0;
|
|
141
|
-
let lastOutputPlayableUntilMs = 0;
|
|
142
|
-
let bargeInInputProcess: BridgeProcess | undefined;
|
|
143
|
-
|
|
144
|
-
const suppressInputForOutput = (audio: Buffer) => {
|
|
145
|
-
const bytesPerMs = params.config.chrome.audioFormat === "g711-ulaw-8khz" ? 8 : 48;
|
|
146
|
-
const durationMs = Math.ceil(audio.byteLength / bytesPerMs);
|
|
147
|
-
const until = Date.now() + durationMs + 900;
|
|
148
|
-
suppressInputUntil = Math.max(suppressInputUntil, until);
|
|
149
|
-
lastOutputPlayableUntilMs = Math.max(lastOutputPlayableUntilMs, until);
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const terminateProcess = (proc: BridgeProcess, signal: NodeJS.Signals = "SIGTERM") => {
|
|
153
|
-
if (proc.killed && signal !== "SIGKILL") {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
let exited = false;
|
|
157
|
-
proc.on("exit", () => {
|
|
158
|
-
exited = true;
|
|
159
|
-
});
|
|
160
|
-
try {
|
|
161
|
-
proc.kill(signal);
|
|
162
|
-
} catch {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (signal === "SIGKILL") {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
const timer = setTimeout(() => {
|
|
169
|
-
if (!exited) {
|
|
170
|
-
try {
|
|
171
|
-
proc.kill("SIGKILL");
|
|
172
|
-
} catch {
|
|
173
|
-
// Process may have exited after the grace check.
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}, 1000);
|
|
177
|
-
timer.unref?.();
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const stop = async () => {
|
|
181
|
-
if (stopped) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
stopped = true;
|
|
185
|
-
try {
|
|
186
|
-
bridge?.close();
|
|
187
|
-
} catch (error) {
|
|
188
|
-
params.logger.debug?.(
|
|
189
|
-
`[google-meet] realtime voice bridge close ignored: ${formatErrorMessage(error)}`,
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
terminateProcess(inputProcess);
|
|
193
|
-
terminateProcess(outputProcess);
|
|
194
|
-
if (bargeInInputProcess) {
|
|
195
|
-
terminateProcess(bargeInInputProcess);
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const fail = (label: string) => (error: Error) => {
|
|
200
|
-
params.logger.warn(`[google-meet] ${label} failed: ${formatErrorMessage(error)}`);
|
|
201
|
-
void stop();
|
|
202
|
-
};
|
|
203
|
-
const attachOutputProcessHandlers = (proc: BridgeProcess) => {
|
|
204
|
-
proc.on("error", (error) => {
|
|
205
|
-
if (proc !== outputProcess) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
fail("audio output command")(error);
|
|
209
|
-
});
|
|
210
|
-
proc.on("exit", (code, signal) => {
|
|
211
|
-
if (proc !== outputProcess) {
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
if (!stopped) {
|
|
215
|
-
params.logger.warn(
|
|
216
|
-
`[google-meet] audio output command exited (${code ?? signal ?? "done"})`,
|
|
217
|
-
);
|
|
218
|
-
void stop();
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
proc.stderr?.on("data", (chunk) => {
|
|
222
|
-
params.logger.debug?.(`[google-meet] audio output: ${String(chunk).trim()}`);
|
|
223
|
-
});
|
|
224
|
-
};
|
|
225
|
-
const clearOutputPlayback = () => {
|
|
226
|
-
if (stopped) {
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
const previousOutput = outputProcess;
|
|
230
|
-
outputProcess = spawnOutputProcess();
|
|
231
|
-
attachOutputProcessHandlers(outputProcess);
|
|
232
|
-
clearCount += 1;
|
|
233
|
-
lastClearAt = new Date().toISOString();
|
|
234
|
-
suppressInputUntil = 0;
|
|
235
|
-
lastOutputPlayableUntilMs = 0;
|
|
236
|
-
params.logger.debug?.(
|
|
237
|
-
`[google-meet] cleared realtime audio output buffer by restarting playback command`,
|
|
238
|
-
);
|
|
239
|
-
terminateProcess(previousOutput, "SIGKILL");
|
|
240
|
-
};
|
|
241
|
-
const startHumanBargeInMonitor = () => {
|
|
242
|
-
const commandArgv = params.config.chrome.bargeInInputCommand;
|
|
243
|
-
if (!commandArgv) {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const command = splitCommand(commandArgv);
|
|
247
|
-
let lastBargeInAt = 0;
|
|
248
|
-
bargeInInputProcess = spawnFn(command.command, command.args, {
|
|
249
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
250
|
-
});
|
|
251
|
-
bargeInInputProcess.stdout?.on("data", (chunk) => {
|
|
252
|
-
if (stopped || lastOutputAtMs === 0) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const now = Date.now();
|
|
256
|
-
const playbackActive = now <= Math.max(lastOutputPlayableUntilMs, suppressInputUntil);
|
|
257
|
-
if (!playbackActive && now - lastOutputAtMs > 1000) {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
if (now - lastBargeInAt < params.config.chrome.bargeInCooldownMs) {
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
const audio = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
264
|
-
const stats = readPcm16Stats(audio);
|
|
265
|
-
if (
|
|
266
|
-
stats.rms < params.config.chrome.bargeInRmsThreshold &&
|
|
267
|
-
stats.peak < params.config.chrome.bargeInPeakThreshold
|
|
268
|
-
) {
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
lastBargeInAt = now;
|
|
272
|
-
suppressInputUntil = 0;
|
|
273
|
-
const beforeClearCount = clearCount;
|
|
274
|
-
bridge?.handleBargeIn({ audioPlaybackActive: true });
|
|
275
|
-
if (beforeClearCount === clearCount) {
|
|
276
|
-
clearOutputPlayback();
|
|
277
|
-
}
|
|
278
|
-
params.logger.debug?.(
|
|
279
|
-
`[google-meet] human barge-in detected by local input (rms=${Math.round(
|
|
280
|
-
stats.rms,
|
|
281
|
-
)}, peak=${stats.peak})`,
|
|
282
|
-
);
|
|
283
|
-
});
|
|
284
|
-
bargeInInputProcess.stderr?.on("data", (chunk) => {
|
|
285
|
-
params.logger.debug?.(`[google-meet] barge-in input: ${String(chunk).trim()}`);
|
|
286
|
-
});
|
|
287
|
-
bargeInInputProcess.on("error", (error) => {
|
|
288
|
-
params.logger.warn(`[google-meet] human barge-in input failed: ${formatErrorMessage(error)}`);
|
|
289
|
-
});
|
|
290
|
-
bargeInInputProcess.on("exit", (code, signal) => {
|
|
291
|
-
if (!stopped) {
|
|
292
|
-
params.logger.debug?.(
|
|
293
|
-
`[google-meet] human barge-in input exited (${code ?? signal ?? "done"})`,
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
};
|
|
298
|
-
inputProcess.on("error", fail("audio input command"));
|
|
299
|
-
inputProcess.on("exit", (code, signal) => {
|
|
300
|
-
if (!stopped) {
|
|
301
|
-
params.logger.warn(`[google-meet] audio input command exited (${code ?? signal ?? "done"})`);
|
|
302
|
-
void stop();
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
attachOutputProcessHandlers(outputProcess);
|
|
306
|
-
inputProcess.stderr?.on("data", (chunk) => {
|
|
307
|
-
params.logger.debug?.(`[google-meet] audio input: ${String(chunk).trim()}`);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
const resolved = resolveGoogleMeetRealtimeProvider({
|
|
311
|
-
config: params.config,
|
|
312
|
-
fullConfig: params.fullConfig,
|
|
313
|
-
providers: params.providers,
|
|
314
|
-
});
|
|
315
|
-
const transcript: Array<{ role: "user" | "assistant"; text: string }> = [];
|
|
316
|
-
bridge = createRealtimeVoiceBridgeSession({
|
|
317
|
-
provider: resolved.provider,
|
|
318
|
-
providerConfig: resolved.providerConfig,
|
|
319
|
-
audioFormat: resolveGoogleMeetRealtimeAudioFormat(params.config),
|
|
320
|
-
instructions: params.config.realtime.instructions,
|
|
321
|
-
initialGreetingInstructions: params.config.realtime.introMessage,
|
|
322
|
-
triggerGreetingOnReady: false,
|
|
323
|
-
markStrategy: "ack-immediately",
|
|
324
|
-
tools: resolveGoogleMeetRealtimeTools(params.config.realtime.toolPolicy),
|
|
325
|
-
audioSink: {
|
|
326
|
-
isOpen: () => !stopped,
|
|
327
|
-
sendAudio: (audio) => {
|
|
328
|
-
lastOutputAtMs = Date.now();
|
|
329
|
-
lastOutputAt = new Date().toISOString();
|
|
330
|
-
lastOutputBytes += audio.byteLength;
|
|
331
|
-
suppressInputForOutput(audio);
|
|
332
|
-
outputProcess.stdin?.write(audio);
|
|
333
|
-
},
|
|
334
|
-
clearAudio: clearOutputPlayback,
|
|
335
|
-
},
|
|
336
|
-
onTranscript: (role, text, isFinal) => {
|
|
337
|
-
if (isFinal) {
|
|
338
|
-
transcript.push({ role, text });
|
|
339
|
-
if (transcript.length > 40) {
|
|
340
|
-
transcript.splice(0, transcript.length - 40);
|
|
341
|
-
}
|
|
342
|
-
params.logger.debug?.(`[google-meet] ${role}: ${text}`);
|
|
343
|
-
}
|
|
344
|
-
},
|
|
345
|
-
onToolCall: (event, session) => {
|
|
346
|
-
if (event.name !== GOOGLE_MEET_AGENT_CONSULT_TOOL_NAME) {
|
|
347
|
-
session.submitToolResult(event.callId || event.itemId, {
|
|
348
|
-
error: `Tool "${event.name}" not available`,
|
|
349
|
-
});
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
submitGoogleMeetConsultWorkingResponse(session, event.callId || event.itemId);
|
|
353
|
-
void consultOpenClawAgentForGoogleMeet({
|
|
354
|
-
config: params.config,
|
|
355
|
-
fullConfig: params.fullConfig,
|
|
356
|
-
runtime: params.runtime,
|
|
357
|
-
logger: params.logger,
|
|
358
|
-
meetingSessionId: params.meetingSessionId,
|
|
359
|
-
args: event.args,
|
|
360
|
-
transcript,
|
|
361
|
-
})
|
|
362
|
-
.then((result) => {
|
|
363
|
-
session.submitToolResult(event.callId || event.itemId, result);
|
|
364
|
-
})
|
|
365
|
-
.catch((error: Error) => {
|
|
366
|
-
session.submitToolResult(event.callId || event.itemId, {
|
|
367
|
-
error: formatErrorMessage(error),
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
},
|
|
371
|
-
onError: fail("realtime voice bridge"),
|
|
372
|
-
onClose: (reason) => {
|
|
373
|
-
realtimeReady = false;
|
|
374
|
-
if (reason === "error") {
|
|
375
|
-
void stop();
|
|
376
|
-
}
|
|
377
|
-
},
|
|
378
|
-
onReady: () => {
|
|
379
|
-
realtimeReady = true;
|
|
380
|
-
},
|
|
381
|
-
});
|
|
382
|
-
startHumanBargeInMonitor();
|
|
383
|
-
|
|
384
|
-
inputProcess.stdout?.on("data", (chunk) => {
|
|
385
|
-
const audio = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
386
|
-
if (!stopped && audio.byteLength > 0) {
|
|
387
|
-
if (Date.now() < suppressInputUntil) {
|
|
388
|
-
lastSuppressedInputAt = new Date().toISOString();
|
|
389
|
-
suppressedInputBytes += audio.byteLength;
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
lastInputAt = new Date().toISOString();
|
|
393
|
-
lastInputBytes += audio.byteLength;
|
|
394
|
-
bridge?.sendAudio(Buffer.from(audio));
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
await bridge.connect();
|
|
399
|
-
return {
|
|
400
|
-
providerId: resolved.provider.id,
|
|
401
|
-
inputCommand: params.inputCommand,
|
|
402
|
-
outputCommand: params.outputCommand,
|
|
403
|
-
speak: (instructions) => {
|
|
404
|
-
bridge?.triggerGreeting(instructions);
|
|
405
|
-
},
|
|
406
|
-
getHealth: () => ({
|
|
407
|
-
providerConnected: bridge?.bridge.isConnected() ?? false,
|
|
408
|
-
realtimeReady,
|
|
409
|
-
audioInputActive: lastInputBytes > 0,
|
|
410
|
-
audioOutputActive: lastOutputBytes > 0,
|
|
411
|
-
lastInputAt,
|
|
412
|
-
lastOutputAt,
|
|
413
|
-
lastSuppressedInputAt,
|
|
414
|
-
lastInputBytes,
|
|
415
|
-
lastOutputBytes,
|
|
416
|
-
suppressedInputBytes,
|
|
417
|
-
lastClearAt,
|
|
418
|
-
clearCount,
|
|
419
|
-
bridgeClosed: stopped,
|
|
420
|
-
}),
|
|
421
|
-
stop,
|
|
422
|
-
};
|
|
423
|
-
}
|