



























@@ -2,6 +2,14 @@ import type { ImageGenerationProvider } from "openclaw/plugin-sdk/image-generati
22import type { MediaUnderstandingProvider } from "openclaw/plugin-sdk/media-understanding";
33import type { MusicGenerationProvider } from "openclaw/plugin-sdk/music-generation";
44import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
5+import type {
6+RealtimeVoiceBridge,
7+RealtimeVoiceBridgeCreateRequest,
8+RealtimeVoiceProviderConfig,
9+RealtimeVoiceProviderPlugin,
10+} from "openclaw/plugin-sdk/realtime-voice";
11+import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
12+import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
513import type { VideoGenerationProvider } from "openclaw/plugin-sdk/video-generation";
614import { buildGoogleGeminiCliBackend } from "./cli-backend.js";
715import { registerGoogleGeminiCliProvider } from "./gemini-cli-provider.js";
@@ -11,13 +19,13 @@ import {
1119} from "./generation-provider-metadata.js";
1220import { geminiMemoryEmbeddingProviderAdapter } from "./memory-embedding-adapter.js";
1321import { registerGoogleProvider } from "./provider-registration.js";
14-import { buildGoogleRealtimeVoiceProvider } from "./realtime-voice-provider.js";
1522import { buildGoogleSpeechProvider } from "./speech-provider.js";
1623import { createGeminiWebSearchProvider } from "./src/gemini-web-search-provider.js";
17241825let googleImageGenerationProviderPromise: Promise<ImageGenerationProvider> | null = null;
1926let googleMediaUnderstandingProviderPromise: Promise<MediaUnderstandingProvider> | null = null;
2027let googleMusicGenerationProviderPromise: Promise<MusicGenerationProvider> | null = null;
28+let googleRealtimeVoiceProviderPromise: Promise<RealtimeVoiceProviderPlugin> | null = null;
2129let googleVideoGenerationProviderPromise: Promise<VideoGenerationProvider> | null = null;
22302331type GoogleMediaUnderstandingProvider = Required<
@@ -54,6 +62,15 @@ async function loadGoogleMusicGenerationProvider(): Promise<MusicGenerationProvi
5462return await googleMusicGenerationProviderPromise;
5563}
566465+async function loadGoogleRealtimeVoiceProvider(): Promise<RealtimeVoiceProviderPlugin> {
66+if (!googleRealtimeVoiceProviderPromise) {
67+googleRealtimeVoiceProviderPromise = import("./realtime-voice-provider.js").then((mod) =>
68+mod.buildGoogleRealtimeVoiceProvider(),
69+);
70+}
71+return await googleRealtimeVoiceProviderPromise;
72+}
73+5774async function loadGoogleVideoGenerationProvider(): Promise<VideoGenerationProvider> {
5875if (!googleVideoGenerationProviderPromise) {
5976googleVideoGenerationProviderPromise = import("./video-generation-provider.js").then((mod) =>
@@ -137,6 +154,113 @@ function createLazyGoogleMusicGenerationProvider(): MusicGenerationProvider {
137154};
138155}
139156157+function resolveGoogleRealtimeProviderConfig(
158+rawConfig: RealtimeVoiceProviderConfig,
159+cfg?: { models?: { providers?: { google?: { apiKey?: unknown } } } },
160+): RealtimeVoiceProviderConfig {
161+const providers =
162+typeof rawConfig.providers === "object" &&
163+rawConfig.providers !== null &&
164+!Array.isArray(rawConfig.providers)
165+ ? (rawConfig.providers as Record<string, unknown>)
166+ : undefined;
167+const nested = providers?.google;
168+const raw =
169+typeof nested === "object" && nested !== null && !Array.isArray(nested)
170+ ? (nested as Record<string, unknown>)
171+ : typeof rawConfig.google === "object" &&
172+rawConfig.google !== null &&
173+!Array.isArray(rawConfig.google)
174+ ? (rawConfig.google as Record<string, unknown>)
175+ : rawConfig;
176+return {
177+ ...raw,
178+ ...(raw.apiKey === undefined
179+ ? cfg?.models?.providers?.google?.apiKey === undefined
180+ ? {}
181+ : {
182+apiKey: normalizeResolvedSecretInputString({
183+value: cfg.models.providers.google.apiKey,
184+path: "models.providers.google.apiKey",
185+}),
186+}
187+ : {
188+apiKey: normalizeResolvedSecretInputString({
189+value: raw.apiKey,
190+path: "plugins.entries.voice-call.config.realtime.providers.google.apiKey",
191+}),
192+}),
193+};
194+}
195+196+function resolveGoogleRealtimeEnvApiKey(): string | undefined {
197+return (
198+normalizeOptionalString(process.env.GEMINI_API_KEY) ??
199+normalizeOptionalString(process.env.GOOGLE_API_KEY)
200+);
201+}
202+203+function createLazyGoogleRealtimeVoiceBridge(
204+req: RealtimeVoiceBridgeCreateRequest,
205+): RealtimeVoiceBridge {
206+let bridge: RealtimeVoiceBridge | undefined;
207+let bridgePromise: Promise<RealtimeVoiceBridge> | undefined;
208+const loadBridge = async () => {
209+if (!bridgePromise) {
210+bridgePromise = loadGoogleRealtimeVoiceProvider().then((provider) =>
211+provider.createBridge(req),
212+);
213+}
214+bridge = await bridgePromise;
215+return bridge;
216+};
217+const requireBridge = () => {
218+if (!bridge) {
219+throw new Error("Google realtime voice bridge is not connected");
220+}
221+return bridge;
222+};
223+return {
224+supportsToolResultContinuation: true,
225+connect: async () => {
226+await (await loadBridge()).connect();
227+},
228+sendAudio: (audio) => requireBridge().sendAudio(audio),
229+setMediaTimestamp: (ts) => requireBridge().setMediaTimestamp(ts),
230+sendUserMessage: (text) => requireBridge().sendUserMessage?.(text),
231+triggerGreeting: (instructions) => requireBridge().triggerGreeting?.(instructions),
232+handleBargeIn: (options) => requireBridge().handleBargeIn?.(options),
233+submitToolResult: (callId, result, options) =>
234+requireBridge().submitToolResult(callId, result, options),
235+acknowledgeMark: () => requireBridge().acknowledgeMark(),
236+close: () => bridge?.close(),
237+isConnected: () => bridge?.isConnected() ?? false,
238+};
239+}
240+241+function createLazyGoogleRealtimeVoiceProvider(): RealtimeVoiceProviderPlugin {
242+return {
243+id: "google",
244+label: "Google Live Voice",
245+autoSelectOrder: 20,
246+resolveConfig: ({ cfg, rawConfig }) => resolveGoogleRealtimeProviderConfig(rawConfig, cfg),
247+isConfigured: ({ cfg, providerConfig }) =>
248+Boolean(
249+normalizeOptionalString(providerConfig.apiKey) ??
250+normalizeOptionalString(cfg?.models?.providers?.google?.apiKey) ??
251+resolveGoogleRealtimeEnvApiKey(),
252+),
253+createBridge: createLazyGoogleRealtimeVoiceBridge,
254+createBrowserSession: async (req) => {
255+const provider = await loadGoogleRealtimeVoiceProvider();
256+if (!provider.createBrowserSession) {
257+throw new Error("Google realtime voice browser sessions are unavailable");
258+}
259+return await provider.createBrowserSession(req);
260+},
261+};
262+}
263+140264function createLazyGoogleVideoGenerationProvider(): VideoGenerationProvider {
141265return {
142266 ...createGoogleVideoGenerationProviderMetadata(),
@@ -157,7 +281,7 @@ export default definePluginEntry({
157281api.registerImageGenerationProvider(createLazyGoogleImageGenerationProvider());
158282api.registerMediaUnderstandingProvider(createLazyGoogleMediaUnderstandingProvider());
159283api.registerMusicGenerationProvider(createLazyGoogleMusicGenerationProvider());
160-api.registerRealtimeVoiceProvider(buildGoogleRealtimeVoiceProvider());
284+api.registerRealtimeVoiceProvider(createLazyGoogleRealtimeVoiceProvider());
161285api.registerSpeechProvider(buildGoogleSpeechProvider());
162286api.registerVideoGenerationProvider(createLazyGoogleVideoGenerationProvider());
163287api.registerWebSearchProvider(createGeminiWebSearchProvider());
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。