























@@ -20,19 +20,25 @@ type MockGoogleLiveConnectParams = {
2020};
2121};
222223-const { connectMock, session } = vi.hoisted(() => {
23+const { connectMock, createTokenMock, session } = vi.hoisted(() => {
2424const session: MockGoogleLiveSession = {
2525close: vi.fn(),
2626sendClientContent: vi.fn(),
2727sendRealtimeInput: vi.fn(),
2828sendToolResponse: vi.fn(),
2929};
3030const connectMock = vi.fn(async (_params: MockGoogleLiveConnectParams) => session);
31-return { connectMock, session };
31+const createTokenMock = vi.fn(async (_params: unknown) => ({
32+name: "auth_tokens/browser-session",
33+}));
34+return { connectMock, createTokenMock, session };
3235});
33363437vi.mock("./google-genai-runtime.js", () => ({
3538createGoogleGenAI: vi.fn(() => ({
39+authTokens: {
40+create: createTokenMock,
41+},
3642live: {
3743connect: connectMock,
3844},
@@ -50,6 +56,7 @@ function lastConnectParams(): MockGoogleLiveConnectParams {
5056describe("buildGoogleRealtimeVoiceProvider", () => {
5157beforeEach(() => {
5258connectMock.mockClear();
59+createTokenMock.mockClear();
5360session.close.mockClear();
5461session.sendClientContent.mockClear();
5562session.sendRealtimeInput.mockClear();
@@ -223,6 +230,88 @@ describe("buildGoogleRealtimeVoiceProvider", () => {
223230expect(lastConnectParams().config).not.toHaveProperty("temperature");
224231});
225232233+it("creates constrained browser sessions for Google Live Talk", async () => {
234+const provider = buildGoogleRealtimeVoiceProvider();
235+236+const session = await provider.createBrowserSession?.({
237+providerConfig: {
238+apiKey: "gemini-key",
239+model: "gemini-live-2.5-flash-preview",
240+voice: "Puck",
241+temperature: 0.4,
242+},
243+instructions: "Speak briefly.",
244+tools: [
245+{
246+type: "function",
247+name: "openclaw_agent_consult",
248+description: "Ask OpenClaw",
249+parameters: {
250+type: "object",
251+properties: {
252+question: { type: "string" },
253+},
254+required: ["question"],
255+},
256+},
257+],
258+});
259+260+expect(createTokenMock).toHaveBeenCalledTimes(1);
261+expect(createTokenMock.mock.calls[0]?.[0]).toMatchObject({
262+config: {
263+uses: 1,
264+liveConnectConstraints: {
265+model: "gemini-live-2.5-flash-preview",
266+config: {
267+responseModalities: ["AUDIO"],
268+temperature: 0.4,
269+systemInstruction: "Speak briefly.",
270+speechConfig: {
271+voiceConfig: {
272+prebuiltVoiceConfig: {
273+voiceName: "Puck",
274+},
275+},
276+},
277+tools: [
278+{
279+functionDeclarations: [
280+{
281+name: "openclaw_agent_consult",
282+behavior: "NON_BLOCKING",
283+},
284+],
285+},
286+],
287+},
288+},
289+},
290+});
291+expect(session).toMatchObject({
292+provider: "google",
293+transport: "json-pcm-websocket",
294+protocol: "google-live-bidi",
295+clientSecret: "auth_tokens/browser-session",
296+websocketUrl:
297+"wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContentConstrained",
298+audio: {
299+inputEncoding: "pcm16",
300+inputSampleRateHz: 16000,
301+outputEncoding: "pcm16",
302+outputSampleRateHz: 24000,
303+},
304+initialMessage: {
305+setup: {
306+model: "models/gemini-live-2.5-flash-preview",
307+generationConfig: {
308+responseModalities: ["AUDIO"],
309+},
310+},
311+},
312+});
313+});
314+226315it("waits for setup completion before draining audio and firing ready", async () => {
227316const provider = buildGoogleRealtimeVoiceProvider();
228317const onReady = vi.fn();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。