UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

216 lines (215 loc) 7.99 kB
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js"; import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { m as normalizeResolvedSecretInputString } from "./types.secrets-kC0nOetj.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import "./secret-input-dpVVFmLG.js"; import { n as createRealtimeVoiceAudioQueue } from "./realtime-session-lifecycle-B2wf87mH.js"; //#region extensions/google/realtime-voice-lazy.ts let googleRealtimeVoiceProviderPromise = null; async function loadGoogleRealtimeVoiceProvider() { if (!googleRealtimeVoiceProviderPromise) googleRealtimeVoiceProviderPromise = import("./extensions/google/realtime-voice-provider.js").then((mod) => mod.buildGoogleRealtimeVoiceProvider()); return await googleRealtimeVoiceProviderPromise; } function resolveGoogleRealtimeProviderConfig(rawConfig, cfg) { const providers = asOptionalRecord(rawConfig.providers); const raw = asOptionalRecord(providers?.google) ?? asOptionalRecord(rawConfig.google) ?? rawConfig; return { ...raw, ...raw.apiKey === void 0 ? cfg?.models?.providers?.google?.apiKey === void 0 ? {} : { apiKey: normalizeResolvedSecretInputString({ value: cfg.models.providers.google.apiKey, path: "models.providers.google.apiKey" }) } : { apiKey: normalizeResolvedSecretInputString({ value: raw.apiKey, path: "plugins.entries.voice-call.config.realtime.providers.google.apiKey" }) } }; } function resolveGoogleRealtimeEnvApiKey() { return normalizeOptionalString(process.env.GEMINI_API_KEY) ?? normalizeOptionalString(process.env.GOOGLE_API_KEY); } const GOOGLE_REALTIME_LAZY_MAX_PENDING_USER_MESSAGES = 128; const GOOGLE_REALTIME_LAZY_MAX_PENDING_USER_MESSAGE_BYTES = 262144; function createLazyGoogleRealtimeVoiceBridge(req) { let bridge; let bridgePromise; let bridgePromiseGeneration = 0; let bridgeReady = false; let terminated = false; let generation = 0; let latestMediaTimestamp; let pendingGreeting; const pendingAudio = createRealtimeVoiceAudioQueue("drop-oldest"); const pendingUserMessages = []; let pendingUserMessageBytes = 0; const closedBridges = /* @__PURE__ */ new WeakSet(); const clearPendingInput = () => { pendingAudio.clear(); pendingUserMessages.length = 0; pendingUserMessageBytes = 0; pendingGreeting = void 0; latestMediaTimestamp = void 0; }; const isCurrentNonterminalGeneration = (candidate) => candidate === generation && !terminated; const closeBridge = (loadedBridge = bridge) => { if (!loadedBridge || closedBridges.has(loadedBridge)) return; closedBridges.add(loadedBridge); loadedBridge.close(); }; const emitTerminal = (terminalGeneration, reason) => { if (!isCurrentNonterminalGeneration(terminalGeneration)) return; bridgeReady = false; terminated = true; clearPendingInput(); req.onClose?.(reason); }; const throwTerminalBridgeError = (terminalGeneration, loadedBridge, primaryError) => { if (isCurrentNonterminalGeneration(terminalGeneration)) { try { req.onError?.(primaryError instanceof Error ? primaryError : new Error(String(primaryError))); } catch {} try { emitTerminal(terminalGeneration, "error"); } catch {} } try { closeBridge(loadedBridge); } catch {} throw primaryError; }; const loadBridge = async () => { if (!bridgePromise) { const loadGeneration = generation; bridgePromiseGeneration = loadGeneration; bridgePromise = loadGoogleRealtimeVoiceProvider().then((provider) => provider.createBridge({ ...req, onReady: () => { if (loadGeneration !== generation || terminated) return; req.onReady?.(); if (loadGeneration !== generation || terminated || !bridge) return; bridgeReady = true; flushPending(bridge); }, onClose: (reason) => { emitTerminal(loadGeneration, reason); } })); } const loading = bridgePromise; const loadGeneration = bridgePromiseGeneration; const loadedBridge = await loading; if (loading !== bridgePromise || loadGeneration !== generation || terminated) { closeBridge(loadedBridge); return loadedBridge; } bridge = loadedBridge; return loadedBridge; }; const requireBridge = () => { if (!bridge) throw new Error("Google realtime voice bridge is not connected"); return bridge; }; const flushPending = (loadedBridge) => { if (terminated) return; if (typeof latestMediaTimestamp === "number") loadedBridge.setMediaTimestamp(latestMediaTimestamp); for (const audio of pendingAudio.drain()) loadedBridge.sendAudio(audio); const userMessages = pendingUserMessages.splice(0); pendingUserMessageBytes = 0; for (const text of userMessages) loadedBridge.sendUserMessage?.(text); if (pendingGreeting !== void 0) { const greeting = pendingGreeting; pendingGreeting = void 0; loadedBridge.triggerGreeting?.(greeting); } }; return { get supportsToolResultContinuation() { return bridge?.supportsToolResultContinuation ?? false; }, supportsToolResultSuppression: false, connect: async () => { if (terminated) { generation += 1; bridge = void 0; bridgePromise = void 0; bridgeReady = false; terminated = false; } const connectGeneration = generation; const loadedBridge = await loadBridge(); if (connectGeneration !== generation || terminated) { closeBridge(loadedBridge); return; } try { await loadedBridge.connect(); } catch (error) { throwTerminalBridgeError(connectGeneration, loadedBridge, error); } if (connectGeneration !== generation || terminated) closeBridge(loadedBridge); }, sendAudio: (audio) => { if (terminated) return; if (bridgeReady && bridge) { bridge.sendAudio(audio); return; } pendingAudio.enqueue(audio); }, setMediaTimestamp: (ts) => { if (terminated) return; latestMediaTimestamp = ts; bridge?.setMediaTimestamp(ts); }, sendUserMessage: (text) => { if (terminated) return; if (bridgeReady && bridge) { bridge.sendUserMessage?.(text); return; } const messageBytes = Buffer.byteLength(text, "utf8"); if (pendingUserMessages.length >= GOOGLE_REALTIME_LAZY_MAX_PENDING_USER_MESSAGES || pendingUserMessageBytes + messageBytes > GOOGLE_REALTIME_LAZY_MAX_PENDING_USER_MESSAGE_BYTES) { req.onError?.(/* @__PURE__ */ new Error("Google realtime voice pending user message queue overflow during startup")); return; } pendingUserMessages.push(text); pendingUserMessageBytes += messageBytes; }, triggerGreeting: (instructions) => { if (terminated) return; if (bridgeReady && bridge) { bridge.triggerGreeting?.(instructions); return; } pendingGreeting = instructions; }, handleBargeIn: (options) => requireBridge().handleBargeIn?.(options), submitToolResult: (callId, result, options) => requireBridge().submitToolResult(callId, result, options), acknowledgeMark: () => requireBridge().acknowledgeMark(), close: () => { if (terminated) return; terminated = true; bridgeReady = false; clearPendingInput(); closeBridge(); req.onClose?.("completed"); }, isConnected: () => !terminated && (bridge?.isConnected() ?? false) }; } function createLazyGoogleRealtimeVoiceProvider() { return { id: "google", label: "Google Live Voice", autoSelectOrder: 20, resolveConfig: ({ cfg, rawConfig }) => resolveGoogleRealtimeProviderConfig(rawConfig, cfg), isConfigured: ({ cfg, providerConfig }) => Boolean(normalizeOptionalString(providerConfig.apiKey) ?? normalizeOptionalString(cfg?.models?.providers?.google?.apiKey) ?? resolveGoogleRealtimeEnvApiKey()), createBridge: createLazyGoogleRealtimeVoiceBridge, createBrowserSession: async (req) => { const provider = await loadGoogleRealtimeVoiceProvider(); if (!provider.createBrowserSession) throw new Error("Google realtime voice browser sessions are unavailable"); return await provider.createBrowserSession(req); } }; } //#endregion export { createLazyGoogleRealtimeVoiceProvider as t };