























@@ -703,7 +703,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
703703}),
704704),
705705);
706-bridge.setMediaTimestamp(1240);
706+bridge.setMediaTimestamp(1300);
707707708708bridge.handleBargeIn?.({ audioPlaybackActive: true });
709709@@ -714,7 +714,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
714714type: "conversation.item.truncate",
715715item_id: "item_1",
716716content_index: 0,
717-audio_end_ms: 240,
717+audio_end_ms: 300,
718718});
719719});
720720@@ -904,6 +904,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
904904"message",
905905Buffer.from(JSON.stringify({ type: "response.created", response: { id: "resp_1" } })),
906906);
907+bridge.setMediaTimestamp(1000);
907908socket.emit(
908909"message",
909910Buffer.from(
@@ -914,6 +915,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
914915}),
915916),
916917);
918+bridge.setMediaTimestamp(1300);
917919918920bridge.handleBargeIn?.({ audioPlaybackActive: true });
919921bridge.handleBargeIn?.({ audioPlaybackActive: true });
@@ -927,7 +929,106 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
927929expect(onEvent).toHaveBeenCalledWith({
928930direction: "client",
929931type: "conversation.item.truncate",
930-detail: "reason=barge-in audioEndMs=0",
932+detail: "reason=barge-in audioEndMs=300",
933+});
934+});
935+936+it("ignores zero-length playback barge-in without clearing audio", async () => {
937+const provider = buildOpenAIRealtimeVoiceProvider();
938+const onClearAudio = vi.fn();
939+const onEvent = vi.fn();
940+const bridge = provider.createBridge({
941+providerConfig: { apiKey: "sk-test" }, // pragma: allowlist secret
942+onAudio: vi.fn(),
943+ onClearAudio,
944+ onEvent,
945+});
946+const connecting = bridge.connect();
947+const socket = FakeWebSocket.instances[0];
948+if (!socket) {
949+throw new Error("expected bridge to create a websocket");
950+}
951+952+socket.readyState = FakeWebSocket.OPEN;
953+socket.emit("open");
954+socket.emit("message", Buffer.from(JSON.stringify({ type: "session.updated" })));
955+await connecting;
956+bridge.setMediaTimestamp(1000);
957+socket.emit(
958+"message",
959+Buffer.from(JSON.stringify({ type: "response.created", response: { id: "resp_1" } })),
960+);
961+socket.emit(
962+"message",
963+Buffer.from(
964+JSON.stringify({
965+type: "response.audio.delta",
966+item_id: "item_1",
967+delta: Buffer.from("assistant audio").toString("base64"),
968+}),
969+),
970+);
971+972+bridge.handleBargeIn?.({ audioPlaybackActive: true });
973+974+expect(onClearAudio).not.toHaveBeenCalled();
975+expect(parseSent(socket)).not.toContainEqual({ type: "response.cancel" });
976+expect(parseSent(socket).some((event) => event.type === "conversation.item.truncate")).toBe(
977+false,
978+);
979+expect(onEvent).toHaveBeenCalledWith({
980+direction: "client",
981+type: "conversation.item.truncate.skipped",
982+detail: "reason=barge-in audioEndMs=0 minAudioEndMs=250",
983+});
984+});
985+986+it("allows immediate playback barge-in when the minimum audio window is zero", async () => {
987+const provider = buildOpenAIRealtimeVoiceProvider();
988+const onClearAudio = vi.fn();
989+const bridge = provider.createBridge({
990+providerConfig: {
991+apiKey: "sk-test", // pragma: allowlist secret
992+minBargeInAudioEndMs: 0,
993+},
994+onAudio: vi.fn(),
995+ onClearAudio,
996+});
997+const connecting = bridge.connect();
998+const socket = FakeWebSocket.instances[0];
999+if (!socket) {
1000+throw new Error("expected bridge to create a websocket");
1001+}
1002+1003+socket.readyState = FakeWebSocket.OPEN;
1004+socket.emit("open");
1005+socket.emit("message", Buffer.from(JSON.stringify({ type: "session.updated" })));
1006+await connecting;
1007+bridge.setMediaTimestamp(1000);
1008+socket.emit(
1009+"message",
1010+Buffer.from(JSON.stringify({ type: "response.created", response: { id: "resp_1" } })),
1011+);
1012+socket.emit(
1013+"message",
1014+Buffer.from(
1015+JSON.stringify({
1016+type: "response.audio.delta",
1017+item_id: "item_1",
1018+delta: Buffer.from("assistant audio").toString("base64"),
1019+}),
1020+),
1021+);
1022+1023+bridge.handleBargeIn?.({ audioPlaybackActive: true });
1024+1025+expect(onClearAudio).toHaveBeenCalledTimes(1);
1026+expect(parseSent(socket)).toContainEqual({ type: "response.cancel" });
1027+expect(parseSent(socket)).toContainEqual({
1028+type: "conversation.item.truncate",
1029+item_id: "item_1",
1030+content_index: 0,
1031+audio_end_ms: 0,
9311032});
9321033});
9331034此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。