


























@@ -2,10 +2,11 @@ import { randomUUID } from "node:crypto";
22import http from "node:http";
33import type { Duplex } from "node:stream";
44import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5-import type {
6-RealtimeVoiceBridge,
7-RealtimeVoiceProviderConfig,
8-RealtimeVoiceProviderPlugin,
5+import {
6+createRealtimeVoiceBridgeSession,
7+type RealtimeVoiceBridgeSession,
8+type RealtimeVoiceProviderConfig,
9+type RealtimeVoiceProviderPlugin,
910} from "openclaw/plugin-sdk/realtime-voice";
1011import WebSocket, { WebSocketServer } from "ws";
1112import type { VoiceCallRealtimeConfig } from "../config.js";
@@ -59,16 +60,7 @@ type CallRegistration = {
5960initialGreetingInstructions?: string;
6061};
616262-type ActiveRealtimeVoiceBridge = Pick<
63-RealtimeVoiceBridge,
64-| "connect"
65-| "sendAudio"
66-| "setMediaTimestamp"
67-| "submitToolResult"
68-| "acknowledgeMark"
69-| "close"
70-| "triggerGreeting"
71->;
63+type ActiveRealtimeVoiceBridge = RealtimeVoiceBridgeSession;
72647365export class RealtimeCallHandler {
7466private readonly toolHandlers = new Map<string, ToolHandlerFn>();
@@ -254,34 +246,30 @@ export class RealtimeCallHandler {
254246this.endCallInManager(callSid, callId, reason);
255247};
256248257-const bridgeRef: { current?: ActiveRealtimeVoiceBridge } = {};
258-const bridge = this.realtimeProvider.createBridge({
249+const bridge = createRealtimeVoiceBridgeSession({
250+ provider: this.realtimeProvider,
259251providerConfig: this.providerConfig,
260252instructions: this.config.instructions,
261253tools: this.config.tools,
262-onAudio: (muLaw) => {
263-if (ws.readyState !== WebSocket.OPEN) {
264-return;
265-}
266-ws.send(
267-JSON.stringify({
268-event: "media",
269- streamSid,
270-media: { payload: muLaw.toString("base64") },
271-}),
272-);
273-},
274-onClearAudio: () => {
275-if (ws.readyState !== WebSocket.OPEN) {
276-return;
277-}
278-ws.send(JSON.stringify({ event: "clear", streamSid }));
279-},
280-onMark: (markName) => {
281-if (ws.readyState !== WebSocket.OPEN) {
282-return;
283-}
284-ws.send(JSON.stringify({ event: "mark", streamSid, mark: { name: markName } }));
254+ initialGreetingInstructions,
255+triggerGreetingOnReady: true,
256+audioSink: {
257+isOpen: () => ws.readyState === WebSocket.OPEN,
258+sendAudio: (muLaw) => {
259+ws.send(
260+JSON.stringify({
261+event: "media",
262+ streamSid,
263+media: { payload: muLaw.toString("base64") },
264+}),
265+);
266+},
267+clearAudio: () => {
268+ws.send(JSON.stringify({ event: "clear", streamSid }));
269+},
270+sendMark: (markName) => {
271+ws.send(JSON.stringify({ event: "mark", streamSid, mark: { name: markName } }));
272+},
285273},
286274onTranscript: (role, text, isFinal) => {
287275if (!isFinal) {
@@ -309,22 +297,15 @@ export class RealtimeCallHandler {
309297 text,
310298});
311299},
312-onToolCall: (toolEvent) => {
313-const activeBridge = bridgeRef.current;
314-if (!activeBridge) {
315-return;
316-}
300+onToolCall: (toolEvent, session) => {
317301void this.executeToolCall(
318-activeBridge,
302+session,
319303callId,
320304toolEvent.callId || toolEvent.itemId,
321305toolEvent.name,
322306toolEvent.args,
323307);
324308},
325-onReady: () => {
326-bridgeRef.current?.triggerGreeting?.(initialGreetingInstructions);
327-},
328309onError: (error) => {
329310console.error("[voice-call] realtime voice error:", error.message);
330311},
@@ -348,8 +329,6 @@ export class RealtimeCallHandler {
348329},
349330});
350331351-bridgeRef.current = bridge;
352-353332bridge.connect().catch((error: Error) => {
354333console.error("[voice-call] Failed to connect realtime bridge:", error);
355334bridge.close();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。