

























@@ -11,12 +11,19 @@ class FakeDataChannel extends EventTarget {
1111}
12121313class FakePeerConnection extends EventTarget {
14+static instances: FakePeerConnection[] = [];
15+1416connectionState: RTCPeerConnectionState = "new";
1517readonly channel = new FakeDataChannel();
1618readonly addTrack = vi.fn();
1719localDescription: RTCSessionDescriptionInit | null = null;
1820remoteDescription: RTCSessionDescriptionInit | null = null;
192122+constructor() {
23+super();
24+FakePeerConnection.instances.push(this);
25+}
26+2027createDataChannel(): RTCDataChannel {
2128return this.channel as unknown as RTCDataChannel;
2229}
@@ -44,6 +51,7 @@ describe("WebRtcSdpRealtimeTalkTransport", () => {
4451});
45524653beforeEach(() => {
54+FakePeerConnection.instances = [];
4755const track = { stop: vi.fn() } as unknown as MediaStreamTrack;
4856const stream = {
4957getAudioTracks: () => [track],
@@ -93,4 +101,75 @@ describe("WebRtcSdpRealtimeTalkTransport", () => {
93101});
94102transport.stop();
95103});
104+105+it("surfaces realtime provider errors from the OpenAI data channel", async () => {
106+vi.stubGlobal(
107+"fetch",
108+vi.fn(async () => new Response("answer-sdp")) as unknown as typeof fetch,
109+);
110+const onStatus = vi.fn();
111+const transport = new WebRtcSdpRealtimeTalkTransport(
112+{
113+provider: "openai",
114+transport: "webrtc-sdp",
115+clientSecret: "client-secret-123",
116+},
117+{
118+client: {} as never,
119+sessionKey: "main",
120+callbacks: { onStatus },
121+},
122+);
123+124+await transport.start();
125+const peer = FakePeerConnection.instances[0];
126+peer?.channel.dispatchEvent(
127+new MessageEvent("message", {
128+data: JSON.stringify({
129+type: "error",
130+error: { message: "Realtime model rejected the session" },
131+}),
132+}),
133+);
134+135+expect(onStatus).toHaveBeenCalledWith("error", "Realtime model rejected the session");
136+transport.stop();
137+});
138+139+it("surfaces speech and response lifecycle status from the OpenAI data channel", async () => {
140+vi.stubGlobal(
141+"fetch",
142+vi.fn(async () => new Response("answer-sdp")) as unknown as typeof fetch,
143+);
144+const onStatus = vi.fn();
145+const transport = new WebRtcSdpRealtimeTalkTransport(
146+{
147+provider: "openai",
148+transport: "webrtc-sdp",
149+clientSecret: "client-secret-123",
150+},
151+{
152+client: {} as never,
153+sessionKey: "main",
154+callbacks: { onStatus },
155+},
156+);
157+158+await transport.start();
159+const peer = FakePeerConnection.instances[0];
160+for (const type of [
161+"input_audio_buffer.speech_started",
162+"input_audio_buffer.speech_stopped",
163+"response.created",
164+"response.done",
165+]) {
166+peer?.channel.dispatchEvent(new MessageEvent("message", { data: JSON.stringify({ type }) }));
167+}
168+169+expect(onStatus).toHaveBeenCalledWith("listening", "Speech detected");
170+expect(onStatus).toHaveBeenCalledWith("thinking", "Processing speech");
171+expect(onStatus).toHaveBeenCalledWith("thinking", "Generating response");
172+expect(onStatus).toHaveBeenCalledWith("listening", undefined);
173+transport.stop();
174+});
96175});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。