






















@@ -8,6 +8,9 @@ const mocks = vi.hoisted(() => ({
88canonicalizeSpeechProviderId: vi.fn((providerId: string | undefined) => providerId),
99getSpeechProvider: vi.fn(),
1010synthesizeSpeech: vi.fn(),
11+getRealtimeVoiceProvider: vi.fn(),
12+resolveConfiguredRealtimeVoiceProvider: vi.fn(),
13+createTalkRealtimeRelaySession: vi.fn(),
1114}));
12151316vi.mock("../../config/config.js", () => ({
@@ -23,6 +26,22 @@ vi.mock("../../tts/tts.js", () => ({
2326synthesizeSpeech: mocks.synthesizeSpeech,
2427}));
252829+vi.mock("../../realtime-voice/provider-registry.js", () => ({
30+getRealtimeVoiceProvider: mocks.getRealtimeVoiceProvider,
31+}));
32+33+vi.mock("../../realtime-voice/provider-resolver.js", () => ({
34+resolveConfiguredRealtimeVoiceProvider: mocks.resolveConfiguredRealtimeVoiceProvider,
35+}));
36+37+vi.mock("../talk-realtime-relay.js", async (importOriginal) => {
38+const actual = await importOriginal<typeof import("../talk-realtime-relay.js")>();
39+return {
40+ ...actual,
41+createTalkRealtimeRelaySession: mocks.createTalkRealtimeRelaySession,
42+};
43+});
44+2645function createTalkConfig(apiKey: unknown): OpenClawConfig {
2746return {
2847talk: {
@@ -112,3 +131,76 @@ describe("talk.speak handler", () => {
112131);
113132});
114133});
134+135+describe("talk.realtime.session handler", () => {
136+beforeEach(() => {
137+vi.clearAllMocks();
138+});
139+140+it("falls back to the gateway relay when Google returns a WebRTC-shaped browser session", async () => {
141+const createBrowserSession = vi.fn(async () => ({
142+provider: "google",
143+clientSecret: "legacy-google-secret",
144+}));
145+const createBridge = vi.fn();
146+const provider = {
147+id: "google",
148+label: "Google Live Voice",
149+isConfigured: () => true,
150+ createBrowserSession,
151+ createBridge,
152+};
153+mocks.getRealtimeVoiceProvider.mockReturnValue(provider);
154+mocks.resolveConfiguredRealtimeVoiceProvider.mockReturnValue({
155+ provider,
156+providerConfig: { apiKey: "gemini-key" },
157+});
158+mocks.createTalkRealtimeRelaySession.mockReturnValue({
159+provider: "google",
160+transport: "gateway-relay",
161+relaySessionId: "relay-1",
162+audio: {
163+inputEncoding: "pcm16",
164+inputSampleRateHz: 24000,
165+outputEncoding: "pcm16",
166+outputSampleRateHz: 24000,
167+},
168+});
169+170+const respond = vi.fn();
171+await talkHandlers["talk.realtime.session"]({
172+req: { type: "req", id: "1", method: "talk.realtime.session" },
173+params: { sessionKey: "main", provider: "google" },
174+client: { connId: "conn-1" } as never,
175+isWebchatConnect: () => false,
176+respond: respond as never,
177+context: {
178+getRuntimeConfig: () =>
179+({
180+talk: {
181+provider: "google",
182+providers: { google: { apiKey: "gemini-key" } },
183+},
184+}) as OpenClawConfig,
185+} as never,
186+});
187+188+expect(createBrowserSession).toHaveBeenCalledTimes(1);
189+expect(mocks.createTalkRealtimeRelaySession).toHaveBeenCalledWith(
190+expect.objectContaining({
191+connId: "conn-1",
192+ provider,
193+providerConfig: { apiKey: "gemini-key" },
194+}),
195+);
196+expect(respond).toHaveBeenCalledWith(
197+true,
198+expect.objectContaining({
199+provider: "google",
200+transport: "gateway-relay",
201+relaySessionId: "relay-1",
202+}),
203+undefined,
204+);
205+});
206+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。