openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
216 lines (215 loc) • 7.99 kB
JavaScript
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 };