@openai/agents-realtime 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +13 -0
- package/dist/bundle/openai-realtime-agents.mjs +8777 -0
- package/dist/bundle/openai-realtime-agents.umd.js +11 -0
- package/dist/clientMessages.d.ts +40 -0
- package/dist/clientMessages.js +2 -0
- package/dist/clientMessages.js.map +1 -0
- package/dist/clientMessages.mjs +2 -0
- package/dist/clientMessages.mjs.map +1 -0
- package/dist/guardrail.d.ts +32 -0
- package/dist/guardrail.js +34 -0
- package/dist/guardrail.js.map +1 -0
- package/dist/guardrail.mjs +34 -0
- package/dist/guardrail.mjs.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +13 -0
- package/dist/index.mjs.map +1 -0
- package/dist/items.d.ts +183 -0
- package/dist/items.js +47 -0
- package/dist/items.js.map +1 -0
- package/dist/items.mjs +47 -0
- package/dist/items.mjs.map +1 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -0
- package/dist/logger.mjs +4 -0
- package/dist/logger.mjs.map +1 -0
- package/dist/metadata.d.ts +9 -0
- package/dist/metadata.js +11 -0
- package/dist/metadata.js.map +1 -0
- package/dist/metadata.mjs +11 -0
- package/dist/metadata.mjs.map +1 -0
- package/dist/openaiRealtimeBase.d.ts +143 -0
- package/dist/openaiRealtimeBase.js +449 -0
- package/dist/openaiRealtimeBase.js.map +1 -0
- package/dist/openaiRealtimeBase.mjs +449 -0
- package/dist/openaiRealtimeBase.mjs.map +1 -0
- package/dist/openaiRealtimeEvents.d.ts +3242 -0
- package/dist/openaiRealtimeEvents.js +439 -0
- package/dist/openaiRealtimeEvents.js.map +1 -0
- package/dist/openaiRealtimeEvents.mjs +439 -0
- package/dist/openaiRealtimeEvents.mjs.map +1 -0
- package/dist/openaiRealtimeWebRtc.d.ts +102 -0
- package/dist/openaiRealtimeWebRtc.js +245 -0
- package/dist/openaiRealtimeWebRtc.js.map +1 -0
- package/dist/openaiRealtimeWebRtc.mjs +245 -0
- package/dist/openaiRealtimeWebRtc.mjs.map +1 -0
- package/dist/openaiRealtimeWebsocket.d.ts +126 -0
- package/dist/openaiRealtimeWebsocket.js +293 -0
- package/dist/openaiRealtimeWebsocket.js.map +1 -0
- package/dist/openaiRealtimeWebsocket.mjs +293 -0
- package/dist/openaiRealtimeWebsocket.mjs.map +1 -0
- package/dist/realtimeAgent.d.ts +49 -0
- package/dist/realtimeAgent.js +37 -0
- package/dist/realtimeAgent.js.map +1 -0
- package/dist/realtimeAgent.mjs +37 -0
- package/dist/realtimeAgent.mjs.map +1 -0
- package/dist/realtimeSession.d.ts +210 -0
- package/dist/realtimeSession.js +469 -0
- package/dist/realtimeSession.js.map +1 -0
- package/dist/realtimeSession.mjs +469 -0
- package/dist/realtimeSession.mjs.map +1 -0
- package/dist/realtimeSessionEvents.d.ts +118 -0
- package/dist/realtimeSessionEvents.js +2 -0
- package/dist/realtimeSessionEvents.js.map +1 -0
- package/dist/realtimeSessionEvents.mjs +2 -0
- package/dist/realtimeSessionEvents.mjs.map +1 -0
- package/dist/shims/shims-browser.d.ts +9 -0
- package/dist/shims/shims-browser.js +6 -0
- package/dist/shims/shims-browser.js.map +1 -0
- package/dist/shims/shims-browser.mjs +6 -0
- package/dist/shims/shims-browser.mjs.map +1 -0
- package/dist/shims/shims-node.d.ts +2 -0
- package/dist/shims/shims-node.js +5 -0
- package/dist/shims/shims-node.js.map +1 -0
- package/dist/shims/shims-node.mjs +5 -0
- package/dist/shims/shims-node.mjs.map +1 -0
- package/dist/shims/shims.d.ts +1 -0
- package/dist/shims/shims.js +2 -0
- package/dist/shims/shims.js.map +1 -0
- package/dist/shims/shims.mjs +2 -0
- package/dist/shims/shims.mjs.map +1 -0
- package/dist/transportLayer.d.ts +96 -0
- package/dist/transportLayer.js +2 -0
- package/dist/transportLayer.js.map +1 -0
- package/dist/transportLayer.mjs +2 -0
- package/dist/transportLayer.mjs.map +1 -0
- package/dist/transportLayerEvents.d.ts +99 -0
- package/dist/transportLayerEvents.js +2 -0
- package/dist/transportLayerEvents.js.map +1 -0
- package/dist/transportLayerEvents.mjs +2 -0
- package/dist/transportLayerEvents.mjs.map +1 -0
- package/dist/utils.d.ts +61 -0
- package/dist/utils.js +183 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +183 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import { isBrowserEnvironment } from '@openai/agents-core/_shims';
|
|
3
|
+
import { UserError } from '@openai/agents-core';
|
|
4
|
+
import logger from "./logger.js";
|
|
5
|
+
import { OpenAIRealtimeBase, } from "./openaiRealtimeBase.js";
|
|
6
|
+
import { parseRealtimeEvent } from "./openaiRealtimeEvents.js";
|
|
7
|
+
import { HEADERS } from "./utils.js";
|
|
8
|
+
/**
|
|
9
|
+
* Transport layer that's handling the connection between the client and OpenAI's Realtime API
|
|
10
|
+
* via WebRTC. While this transport layer is designed to be used within a RealtimeSession, it can
|
|
11
|
+
* also be used standalone if you want to have a direct connection to the Realtime API.
|
|
12
|
+
*
|
|
13
|
+
* Unless you specify a `mediaStream` or `audioElement` option, the transport layer will
|
|
14
|
+
* automatically configure the microphone and audio output to be used by the session.
|
|
15
|
+
*/
|
|
16
|
+
export class OpenAIRealtimeWebRTC extends OpenAIRealtimeBase {
|
|
17
|
+
options;
|
|
18
|
+
#url;
|
|
19
|
+
#state = {
|
|
20
|
+
status: 'disconnected',
|
|
21
|
+
peerConnection: undefined,
|
|
22
|
+
dataChannel: undefined,
|
|
23
|
+
};
|
|
24
|
+
#useInsecureApiKey;
|
|
25
|
+
#ongoingResponse = false;
|
|
26
|
+
#muted = false;
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
if (typeof RTCPeerConnection === 'undefined') {
|
|
29
|
+
throw new Error('WebRTC is not supported in this environment');
|
|
30
|
+
}
|
|
31
|
+
super(options);
|
|
32
|
+
this.options = options;
|
|
33
|
+
this.#url = options.baseUrl ?? `https://api.openai.com/v1/realtime`;
|
|
34
|
+
this.#useInsecureApiKey = options.useInsecureApiKey ?? false;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The current status of the WebRTC connection.
|
|
38
|
+
*/
|
|
39
|
+
get status() {
|
|
40
|
+
return this.#state.status;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The current connection state of the WebRTC connection including the peer connection and data
|
|
44
|
+
* channel.
|
|
45
|
+
*/
|
|
46
|
+
get connectionState() {
|
|
47
|
+
return this.#state;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Whether the session is muted.
|
|
51
|
+
*/
|
|
52
|
+
get muted() {
|
|
53
|
+
return this.#muted;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Connect to the Realtime API. This will establish the connection to the OpenAI Realtime API
|
|
57
|
+
* via WebRTC.
|
|
58
|
+
*
|
|
59
|
+
* If you are using a browser, the transport layer will also automatically configure the
|
|
60
|
+
* microphone and audio output to be used by the session.
|
|
61
|
+
*
|
|
62
|
+
* @param options - The options for the connection.
|
|
63
|
+
*/
|
|
64
|
+
async connect(options) {
|
|
65
|
+
if (this.#state.status === 'connected') {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (this.#state.status === 'connecting') {
|
|
69
|
+
logger.warn('Realtime connection already in progress. Please await original promise');
|
|
70
|
+
}
|
|
71
|
+
const model = options.model ?? this.currentModel;
|
|
72
|
+
this.currentModel = model;
|
|
73
|
+
const baseUrl = options.url ?? this.#url;
|
|
74
|
+
const apiKey = await this._getApiKey(options);
|
|
75
|
+
const isClientKey = typeof apiKey === 'string' && apiKey.startsWith('ek_');
|
|
76
|
+
if (isBrowserEnvironment() && !this.#useInsecureApiKey && !isClientKey) {
|
|
77
|
+
throw new UserError('Using the WebRTC connection in a browser environment requires an insecure API key. Please use a WebSocket connection instead or set the useInsecureApiKey option to true.');
|
|
78
|
+
}
|
|
79
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
80
|
+
return new Promise(async (resolve, reject) => {
|
|
81
|
+
try {
|
|
82
|
+
const userSessionConfig = {
|
|
83
|
+
...(options.initialSessionConfig || {}),
|
|
84
|
+
model: this.currentModel,
|
|
85
|
+
};
|
|
86
|
+
const connectionUrl = new URL(baseUrl);
|
|
87
|
+
const peerConnection = new RTCPeerConnection();
|
|
88
|
+
const dataChannel = peerConnection.createDataChannel('oai-events');
|
|
89
|
+
this.#state = {
|
|
90
|
+
status: 'connecting',
|
|
91
|
+
peerConnection,
|
|
92
|
+
dataChannel,
|
|
93
|
+
};
|
|
94
|
+
this.emit('connection_change', this.#state.status);
|
|
95
|
+
dataChannel.addEventListener('open', () => {
|
|
96
|
+
this.#state = {
|
|
97
|
+
status: 'connected',
|
|
98
|
+
peerConnection,
|
|
99
|
+
dataChannel,
|
|
100
|
+
};
|
|
101
|
+
// Sending the session config again here once the channel is connected to ensure
|
|
102
|
+
// that the session config is sent to the server before the first response is received
|
|
103
|
+
// Setting it on connection should work but the config is not being validated on the
|
|
104
|
+
// server. This triggers a validation error if the config is not valid.
|
|
105
|
+
this.updateSessionConfig(userSessionConfig);
|
|
106
|
+
this.emit('connection_change', this.#state.status);
|
|
107
|
+
this._onOpen();
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
dataChannel.addEventListener('error', (event) => {
|
|
111
|
+
this.close();
|
|
112
|
+
this._onError(event);
|
|
113
|
+
reject(event);
|
|
114
|
+
});
|
|
115
|
+
dataChannel.addEventListener('message', (event) => {
|
|
116
|
+
this._onMessage(event);
|
|
117
|
+
const { data: parsed, isGeneric } = parseRealtimeEvent(event);
|
|
118
|
+
if (!parsed || isGeneric) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (parsed.type === 'response.created') {
|
|
122
|
+
this.#ongoingResponse = true;
|
|
123
|
+
}
|
|
124
|
+
else if (parsed.type === 'response.done') {
|
|
125
|
+
this.#ongoingResponse = false;
|
|
126
|
+
}
|
|
127
|
+
if (parsed.type === 'session.created') {
|
|
128
|
+
this._tracingConfig = parsed.session.tracing;
|
|
129
|
+
// Trying to turn on tracing after the session is created
|
|
130
|
+
this._updateTracingConfig(userSessionConfig.tracing ?? 'auto');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// set up audio playback
|
|
134
|
+
const audioElement = this.options.audioElement ?? document.createElement('audio');
|
|
135
|
+
audioElement.autoplay = true;
|
|
136
|
+
peerConnection.ontrack = (event) => {
|
|
137
|
+
audioElement.srcObject = event.streams[0];
|
|
138
|
+
};
|
|
139
|
+
// get microphone stream
|
|
140
|
+
const stream = this.options.mediaStream ??
|
|
141
|
+
(await navigator.mediaDevices.getUserMedia({
|
|
142
|
+
audio: true,
|
|
143
|
+
}));
|
|
144
|
+
peerConnection.addTrack(stream.getAudioTracks()[0]);
|
|
145
|
+
const offer = await peerConnection.createOffer();
|
|
146
|
+
await peerConnection.setLocalDescription(offer);
|
|
147
|
+
if (!offer.sdp) {
|
|
148
|
+
throw new Error('Failed to create offer');
|
|
149
|
+
}
|
|
150
|
+
const sessionConfig = {
|
|
151
|
+
...this._getMergedSessionConfig(userSessionConfig),
|
|
152
|
+
model: this.currentModel,
|
|
153
|
+
};
|
|
154
|
+
const data = new FormData();
|
|
155
|
+
data.append('sdp', offer.sdp);
|
|
156
|
+
data.append('session', JSON.stringify(sessionConfig));
|
|
157
|
+
const sdpResponse = await fetch(connectionUrl, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
body: data,
|
|
160
|
+
headers: {
|
|
161
|
+
Authorization: `Bearer ${apiKey}`,
|
|
162
|
+
'X-OpenAI-Agents-SDK': HEADERS['X-OpenAI-Agents-SDK'],
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
const answer = {
|
|
166
|
+
type: 'answer',
|
|
167
|
+
sdp: await sdpResponse.text(),
|
|
168
|
+
};
|
|
169
|
+
await peerConnection.setRemoteDescription(answer);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
this.close();
|
|
173
|
+
this._onError(error);
|
|
174
|
+
reject(error);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Send an event to the Realtime API. This will stringify the event and send it directly to the
|
|
180
|
+
* API. This can be used if you want to take control over the connection and send events manually.
|
|
181
|
+
*
|
|
182
|
+
* @param event - The event to send.
|
|
183
|
+
*/
|
|
184
|
+
sendEvent(event) {
|
|
185
|
+
if (!this.#state.dataChannel ||
|
|
186
|
+
this.#state.dataChannel.readyState !== 'open') {
|
|
187
|
+
throw new Error('WebRTC data channel is not connected. Make sure you call `connect()` before sending events.');
|
|
188
|
+
}
|
|
189
|
+
this.#state.dataChannel.send(JSON.stringify(event));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Mute or unmute the session.
|
|
193
|
+
* @param muted - Whether to mute the session.
|
|
194
|
+
*/
|
|
195
|
+
mute(muted) {
|
|
196
|
+
this.#muted = muted;
|
|
197
|
+
if (this.#state.peerConnection) {
|
|
198
|
+
const peerConnection = this.#state.peerConnection;
|
|
199
|
+
peerConnection.getSenders().forEach((sender) => {
|
|
200
|
+
if (sender.track) {
|
|
201
|
+
sender.track.enabled = !muted;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Close the connection to the Realtime API and disconnects the underlying WebRTC connection.
|
|
208
|
+
*/
|
|
209
|
+
close() {
|
|
210
|
+
if (this.#state.dataChannel) {
|
|
211
|
+
this.#state.dataChannel.close();
|
|
212
|
+
}
|
|
213
|
+
if (this.#state.peerConnection) {
|
|
214
|
+
const peerConnection = this.#state.peerConnection;
|
|
215
|
+
peerConnection.getSenders().forEach((sender) => {
|
|
216
|
+
sender.track?.stop();
|
|
217
|
+
});
|
|
218
|
+
peerConnection.close();
|
|
219
|
+
}
|
|
220
|
+
if (this.#state.status !== 'disconnected') {
|
|
221
|
+
this.#state = {
|
|
222
|
+
status: 'disconnected',
|
|
223
|
+
peerConnection: undefined,
|
|
224
|
+
dataChannel: undefined,
|
|
225
|
+
};
|
|
226
|
+
this.emit('connection_change', this.#state.status);
|
|
227
|
+
this._onClose();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Interrupt the current response if one is ongoing and clear the audio buffer so that the agent
|
|
232
|
+
* stops talking.
|
|
233
|
+
*/
|
|
234
|
+
interrupt() {
|
|
235
|
+
if (this.#ongoingResponse) {
|
|
236
|
+
this.sendEvent({
|
|
237
|
+
type: 'response.cancel',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
this.sendEvent({
|
|
241
|
+
type: 'output_audio_buffer.clear',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=openaiRealtimeWebRtc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openaiRealtimeWebRtc.js","sourceRoot":"","sources":["../src/openaiRealtimeWebRtc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import { isBrowserEnvironment } from '@openai/agents-core/_shims';
|
|
3
|
+
import { UserError } from '@openai/agents-core';
|
|
4
|
+
import logger from "./logger.mjs";
|
|
5
|
+
import { OpenAIRealtimeBase, } from "./openaiRealtimeBase.mjs";
|
|
6
|
+
import { parseRealtimeEvent } from "./openaiRealtimeEvents.mjs";
|
|
7
|
+
import { HEADERS } from "./utils.mjs";
|
|
8
|
+
/**
|
|
9
|
+
* Transport layer that's handling the connection between the client and OpenAI's Realtime API
|
|
10
|
+
* via WebRTC. While this transport layer is designed to be used within a RealtimeSession, it can
|
|
11
|
+
* also be used standalone if you want to have a direct connection to the Realtime API.
|
|
12
|
+
*
|
|
13
|
+
* Unless you specify a `mediaStream` or `audioElement` option, the transport layer will
|
|
14
|
+
* automatically configure the microphone and audio output to be used by the session.
|
|
15
|
+
*/
|
|
16
|
+
export class OpenAIRealtimeWebRTC extends OpenAIRealtimeBase {
|
|
17
|
+
options;
|
|
18
|
+
#url;
|
|
19
|
+
#state = {
|
|
20
|
+
status: 'disconnected',
|
|
21
|
+
peerConnection: undefined,
|
|
22
|
+
dataChannel: undefined,
|
|
23
|
+
};
|
|
24
|
+
#useInsecureApiKey;
|
|
25
|
+
#ongoingResponse = false;
|
|
26
|
+
#muted = false;
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
if (typeof RTCPeerConnection === 'undefined') {
|
|
29
|
+
throw new Error('WebRTC is not supported in this environment');
|
|
30
|
+
}
|
|
31
|
+
super(options);
|
|
32
|
+
this.options = options;
|
|
33
|
+
this.#url = options.baseUrl ?? `https://api.openai.com/v1/realtime`;
|
|
34
|
+
this.#useInsecureApiKey = options.useInsecureApiKey ?? false;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The current status of the WebRTC connection.
|
|
38
|
+
*/
|
|
39
|
+
get status() {
|
|
40
|
+
return this.#state.status;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The current connection state of the WebRTC connection including the peer connection and data
|
|
44
|
+
* channel.
|
|
45
|
+
*/
|
|
46
|
+
get connectionState() {
|
|
47
|
+
return this.#state;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Whether the session is muted.
|
|
51
|
+
*/
|
|
52
|
+
get muted() {
|
|
53
|
+
return this.#muted;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Connect to the Realtime API. This will establish the connection to the OpenAI Realtime API
|
|
57
|
+
* via WebRTC.
|
|
58
|
+
*
|
|
59
|
+
* If you are using a browser, the transport layer will also automatically configure the
|
|
60
|
+
* microphone and audio output to be used by the session.
|
|
61
|
+
*
|
|
62
|
+
* @param options - The options for the connection.
|
|
63
|
+
*/
|
|
64
|
+
async connect(options) {
|
|
65
|
+
if (this.#state.status === 'connected') {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (this.#state.status === 'connecting') {
|
|
69
|
+
logger.warn('Realtime connection already in progress. Please await original promise');
|
|
70
|
+
}
|
|
71
|
+
const model = options.model ?? this.currentModel;
|
|
72
|
+
this.currentModel = model;
|
|
73
|
+
const baseUrl = options.url ?? this.#url;
|
|
74
|
+
const apiKey = await this._getApiKey(options);
|
|
75
|
+
const isClientKey = typeof apiKey === 'string' && apiKey.startsWith('ek_');
|
|
76
|
+
if (isBrowserEnvironment() && !this.#useInsecureApiKey && !isClientKey) {
|
|
77
|
+
throw new UserError('Using the WebRTC connection in a browser environment requires an insecure API key. Please use a WebSocket connection instead or set the useInsecureApiKey option to true.');
|
|
78
|
+
}
|
|
79
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
80
|
+
return new Promise(async (resolve, reject) => {
|
|
81
|
+
try {
|
|
82
|
+
const userSessionConfig = {
|
|
83
|
+
...(options.initialSessionConfig || {}),
|
|
84
|
+
model: this.currentModel,
|
|
85
|
+
};
|
|
86
|
+
const connectionUrl = new URL(baseUrl);
|
|
87
|
+
const peerConnection = new RTCPeerConnection();
|
|
88
|
+
const dataChannel = peerConnection.createDataChannel('oai-events');
|
|
89
|
+
this.#state = {
|
|
90
|
+
status: 'connecting',
|
|
91
|
+
peerConnection,
|
|
92
|
+
dataChannel,
|
|
93
|
+
};
|
|
94
|
+
this.emit('connection_change', this.#state.status);
|
|
95
|
+
dataChannel.addEventListener('open', () => {
|
|
96
|
+
this.#state = {
|
|
97
|
+
status: 'connected',
|
|
98
|
+
peerConnection,
|
|
99
|
+
dataChannel,
|
|
100
|
+
};
|
|
101
|
+
// Sending the session config again here once the channel is connected to ensure
|
|
102
|
+
// that the session config is sent to the server before the first response is received
|
|
103
|
+
// Setting it on connection should work but the config is not being validated on the
|
|
104
|
+
// server. This triggers a validation error if the config is not valid.
|
|
105
|
+
this.updateSessionConfig(userSessionConfig);
|
|
106
|
+
this.emit('connection_change', this.#state.status);
|
|
107
|
+
this._onOpen();
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
dataChannel.addEventListener('error', (event) => {
|
|
111
|
+
this.close();
|
|
112
|
+
this._onError(event);
|
|
113
|
+
reject(event);
|
|
114
|
+
});
|
|
115
|
+
dataChannel.addEventListener('message', (event) => {
|
|
116
|
+
this._onMessage(event);
|
|
117
|
+
const { data: parsed, isGeneric } = parseRealtimeEvent(event);
|
|
118
|
+
if (!parsed || isGeneric) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (parsed.type === 'response.created') {
|
|
122
|
+
this.#ongoingResponse = true;
|
|
123
|
+
}
|
|
124
|
+
else if (parsed.type === 'response.done') {
|
|
125
|
+
this.#ongoingResponse = false;
|
|
126
|
+
}
|
|
127
|
+
if (parsed.type === 'session.created') {
|
|
128
|
+
this._tracingConfig = parsed.session.tracing;
|
|
129
|
+
// Trying to turn on tracing after the session is created
|
|
130
|
+
this._updateTracingConfig(userSessionConfig.tracing ?? 'auto');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// set up audio playback
|
|
134
|
+
const audioElement = this.options.audioElement ?? document.createElement('audio');
|
|
135
|
+
audioElement.autoplay = true;
|
|
136
|
+
peerConnection.ontrack = (event) => {
|
|
137
|
+
audioElement.srcObject = event.streams[0];
|
|
138
|
+
};
|
|
139
|
+
// get microphone stream
|
|
140
|
+
const stream = this.options.mediaStream ??
|
|
141
|
+
(await navigator.mediaDevices.getUserMedia({
|
|
142
|
+
audio: true,
|
|
143
|
+
}));
|
|
144
|
+
peerConnection.addTrack(stream.getAudioTracks()[0]);
|
|
145
|
+
const offer = await peerConnection.createOffer();
|
|
146
|
+
await peerConnection.setLocalDescription(offer);
|
|
147
|
+
if (!offer.sdp) {
|
|
148
|
+
throw new Error('Failed to create offer');
|
|
149
|
+
}
|
|
150
|
+
const sessionConfig = {
|
|
151
|
+
...this._getMergedSessionConfig(userSessionConfig),
|
|
152
|
+
model: this.currentModel,
|
|
153
|
+
};
|
|
154
|
+
const data = new FormData();
|
|
155
|
+
data.append('sdp', offer.sdp);
|
|
156
|
+
data.append('session', JSON.stringify(sessionConfig));
|
|
157
|
+
const sdpResponse = await fetch(connectionUrl, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
body: data,
|
|
160
|
+
headers: {
|
|
161
|
+
Authorization: `Bearer ${apiKey}`,
|
|
162
|
+
'X-OpenAI-Agents-SDK': HEADERS['X-OpenAI-Agents-SDK'],
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
const answer = {
|
|
166
|
+
type: 'answer',
|
|
167
|
+
sdp: await sdpResponse.text(),
|
|
168
|
+
};
|
|
169
|
+
await peerConnection.setRemoteDescription(answer);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
this.close();
|
|
173
|
+
this._onError(error);
|
|
174
|
+
reject(error);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Send an event to the Realtime API. This will stringify the event and send it directly to the
|
|
180
|
+
* API. This can be used if you want to take control over the connection and send events manually.
|
|
181
|
+
*
|
|
182
|
+
* @param event - The event to send.
|
|
183
|
+
*/
|
|
184
|
+
sendEvent(event) {
|
|
185
|
+
if (!this.#state.dataChannel ||
|
|
186
|
+
this.#state.dataChannel.readyState !== 'open') {
|
|
187
|
+
throw new Error('WebRTC data channel is not connected. Make sure you call `connect()` before sending events.');
|
|
188
|
+
}
|
|
189
|
+
this.#state.dataChannel.send(JSON.stringify(event));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Mute or unmute the session.
|
|
193
|
+
* @param muted - Whether to mute the session.
|
|
194
|
+
*/
|
|
195
|
+
mute(muted) {
|
|
196
|
+
this.#muted = muted;
|
|
197
|
+
if (this.#state.peerConnection) {
|
|
198
|
+
const peerConnection = this.#state.peerConnection;
|
|
199
|
+
peerConnection.getSenders().forEach((sender) => {
|
|
200
|
+
if (sender.track) {
|
|
201
|
+
sender.track.enabled = !muted;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Close the connection to the Realtime API and disconnects the underlying WebRTC connection.
|
|
208
|
+
*/
|
|
209
|
+
close() {
|
|
210
|
+
if (this.#state.dataChannel) {
|
|
211
|
+
this.#state.dataChannel.close();
|
|
212
|
+
}
|
|
213
|
+
if (this.#state.peerConnection) {
|
|
214
|
+
const peerConnection = this.#state.peerConnection;
|
|
215
|
+
peerConnection.getSenders().forEach((sender) => {
|
|
216
|
+
sender.track?.stop();
|
|
217
|
+
});
|
|
218
|
+
peerConnection.close();
|
|
219
|
+
}
|
|
220
|
+
if (this.#state.status !== 'disconnected') {
|
|
221
|
+
this.#state = {
|
|
222
|
+
status: 'disconnected',
|
|
223
|
+
peerConnection: undefined,
|
|
224
|
+
dataChannel: undefined,
|
|
225
|
+
};
|
|
226
|
+
this.emit('connection_change', this.#state.status);
|
|
227
|
+
this._onClose();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Interrupt the current response if one is ongoing and clear the audio buffer so that the agent
|
|
232
|
+
* stops talking.
|
|
233
|
+
*/
|
|
234
|
+
interrupt() {
|
|
235
|
+
if (this.#ongoingResponse) {
|
|
236
|
+
this.sendEvent({
|
|
237
|
+
type: 'response.cancel',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
this.sendEvent({
|
|
241
|
+
type: 'output_audio_buffer.clear',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=openaiRealtimeWebRtc.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openaiRealtimeWebRtc.mjs","sourceRoot":"","sources":["../src/openaiRealtimeWebRtc.ts"],"names":[],"mappings":"AAAA,2BAA2B;OAEpB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B;OAM1D,EAAE,SAAS,EAAE,MAAM,qBAAqB;OACxC,MAAM;OAEN,EACL,kBAAkB,GAEnB;OACM,EAAE,kBAAkB,EAAE;OACtB,EAAE,OAAO,EAAE;AAiDlB;;;;;;;GAOG;AACH,MAAM,OAAO,oBACX,SAAQ,kBAAkB;IAaG;IAV7B,IAAI,CAAS;IACb,MAAM,GAAgB;QACpB,MAAM,EAAE,cAAc;QACtB,cAAc,EAAE,SAAS;QACzB,WAAW,EAAE,SAAS;KACvB,CAAC;IACF,kBAAkB,CAAU;IAC5B,gBAAgB,GAAY,KAAK,CAAC;IAClC,MAAM,GAAG,KAAK,CAAC;IAEf,YAA6B,UAAuC,EAAE;QACpE,IAAI,OAAO,iBAAiB,KAAK,WAAW,EAAE,CAAC;YAC7C,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACjE,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,CAAC;QAJY,YAAO,GAAP,OAAO,CAAkC;QAKpE,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,OAAO,IAAI,oCAAoC,CAAC;QACpE,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,IAAI,KAAK,CAAC;IAC/D,CAAC;IAED;;OAEG;IACH,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACH,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;OAEG;IACH,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,OAAO,CAAC,OAA6C;QACzD,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YACvC,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;YACxC,MAAM,CAAC,IAAI,CACT,wEAAwE,CACzE,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC;QACjD,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAE9C,MAAM,WAAW,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC3E,IAAI,oBAAoB,EAAE,IAAI,CAAC,IAAI,CAAC,kBAAkB,IAAI,CAAC,WAAW,EAAE,CAAC;YACvE,MAAM,IAAI,SAAS,CACjB,2KAA2K,CAC5K,CAAC;QACJ,CAAC;QAED,qDAAqD;QACrD,OAAO,IAAI,OAAO,CAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;YACjD,IAAI,CAAC;gBACH,MAAM,iBAAiB,GAAmC;oBACxD,GAAG,CAAC,OAAO,CAAC,oBAAoB,IAAI,EAAE,CAAC;oBACvC,KAAK,EAAE,IAAI,CAAC,YAAY;iBACzB,CAAC;gBAEF,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;gBAEvC,MAAM,cAAc,GAAG,IAAI,iBAAiB,EAAE,CAAC;gBAC/C,MAAM,WAAW,GAAG,cAAc,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC;gBAEnE,IAAI,CAAC,MAAM,GAAG;oBACZ,MAAM,EAAE,YAAY;oBACpB,cAAc;oBACd,WAAW;iBACZ,CAAC;gBACF,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAEnD,WAAW,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE;oBACxC,IAAI,CAAC,MAAM,GAAG;wBACZ,MAAM,EAAE,WAAW;wBACnB,cAAc;wBACd,WAAW;qBACZ,CAAC;oBACF,gFAAgF;oBAChF,sFAAsF;oBACtF,oFAAoF;oBACpF,uEAAuE;oBACvE,IAAI,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;oBAC5C,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBACnD,IAAI,CAAC,OAAO,EAAE,CAAC;oBACf,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;gBAEH,WAAW,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;oBAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;oBACb,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;oBACrB,MAAM,CAAC,KAAK,CAAC,CAAC;gBAChB,CAAC,CAAC,CAAC;gBAEH,WAAW,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;oBAChD,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;oBACvB,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;oBAC9D,IAAI,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;wBACzB,OAAO;oBACT,CAAC;oBACD,IAAI,MAAM,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;wBACvC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;oBAC/B,CAAC;yBAAM,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;wBAC3C,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;oBAChC,CAAC;oBAED,IAAI,MAAM,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;wBACtC,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;wBAC7C,yDAAyD;wBACzD,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC;oBACjE,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,wBAAwB;gBACxB,MAAM,YAAY,GAChB,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;gBAC/D,YAAY,CAAC,QAAQ,GAAG,IAAI,CAAC;gBAC7B,cAAc,CAAC,OAAO,GAAG,CAAC,KAAK,EAAE,EAAE;oBACjC,YAAY,CAAC,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBAC5C,CAAC,CAAC;gBAEF,wBAAwB;gBACxB,MAAM,MAAM,GACV,IAAI,CAAC,OAAO,CAAC,WAAW;oBACxB,CAAC,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC;wBACzC,KAAK,EAAE,IAAI;qBACZ,CAAC,CAAC,CAAC;gBACN,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBAEpD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,WAAW,EAAE,CAAC;gBACjD,MAAM,cAAc,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;gBAEhD,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;oBACf,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;gBAC5C,CAAC;gBAED,MAAM,aAAa,GAAG;oBACpB,GAAG,IAAI,CAAC,uBAAuB,CAAC,iBAAiB,CAAC;oBAClD,KAAK,EAAE,IAAI,CAAC,YAAY;iBACzB,CAAC;gBAEF,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;gBAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;gBAC9B,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;gBAEtD,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,aAAa,EAAE;oBAC7C,MAAM,EAAE,MAAM;oBACd,IAAI,EAAE,IAAI;oBACV,OAAO,EAAE;wBACP,aAAa,EAAE,UAAU,MAAM,EAAE;wBACjC,qBAAqB,EAAE,OAAO,CAAC,qBAAqB,CAAC;qBACtD;iBACF,CAAC,CAAC;gBAEH,MAAM,MAAM,GAA8B;oBACxC,IAAI,EAAE,QAAQ;oBACd,GAAG,EAAE,MAAM,WAAW,CAAC,IAAI,EAAE;iBAC9B,CAAC;gBAEF,MAAM,cAAc,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;YACpD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,KAAK,EAAE,CAAC;gBACb,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBACrB,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,KAA4B;QACpC,IACE,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW;YACxB,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,KAAK,MAAM,EAC7C,CAAC;YACD,MAAM,IAAI,KAAK,CACb,6FAA6F,CAC9F,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,IAAI,CAAC,KAAc;QACjB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YAC/B,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;YAClD,cAAc,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;gBAC7C,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;oBACjB,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC;gBAChC,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAC5B,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QAClC,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YAC/B,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;YAClD,cAAc,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;gBAC7C,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;YACvB,CAAC,CAAC,CAAC;YACH,cAAc,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;YAC1C,IAAI,CAAC,MAAM,GAAG;gBACZ,MAAM,EAAE,cAAc;gBACtB,cAAc,EAAE,SAAS;gBACzB,WAAW,EAAE,SAAS;aACvB,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACnD,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,SAAS;QACP,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,IAAI,CAAC,SAAS,CAAC;gBACb,IAAI,EAAE,iBAAiB;aACxB,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,SAAS,CAAC;YACb,IAAI,EAAE,2BAA2B;SAClC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { WebSocket } from '@openai/agents-realtime/_shims';
|
|
2
|
+
import { RealtimeTransportLayerConnectOptions, RealtimeTransportLayer } from './transportLayer';
|
|
3
|
+
import { RealtimeClientMessage } from './clientMessages';
|
|
4
|
+
import { OpenAIRealtimeBase, OpenAIRealtimeBaseOptions } from './openaiRealtimeBase';
|
|
5
|
+
import { TransportLayerAudio } from './transportLayerEvents';
|
|
6
|
+
/**
|
|
7
|
+
* The connection state of the WebSocket connection.
|
|
8
|
+
*/
|
|
9
|
+
export type WebSocketState = {
|
|
10
|
+
status: 'disconnected';
|
|
11
|
+
websocket: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
status: 'connecting';
|
|
14
|
+
websocket: WebSocket;
|
|
15
|
+
} | {
|
|
16
|
+
status: 'connected';
|
|
17
|
+
websocket: WebSocket;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The options for the OpenAI Realtime WebSocket transport layer.
|
|
21
|
+
*/
|
|
22
|
+
export type OpenAIRealtimeWebSocketOptions = {
|
|
23
|
+
/**
|
|
24
|
+
* **Important**: Do not use this option unless you know what you are doing.
|
|
25
|
+
*
|
|
26
|
+
* Whether to use an insecure API key. This has to be set if you are trying to use a regular
|
|
27
|
+
* OpenAI API key instead of a client ephemeral key.
|
|
28
|
+
* @see https://platform.openai.com/docs/guides/realtime#creating-an-ephemeral-token
|
|
29
|
+
*/
|
|
30
|
+
useInsecureApiKey?: boolean;
|
|
31
|
+
} & OpenAIRealtimeBaseOptions;
|
|
32
|
+
/**
|
|
33
|
+
* Transport layer that's handling the connection between the client and OpenAI's Realtime API
|
|
34
|
+
* via WebSockets. While this transport layer is designed to be used within a RealtimeSession, it
|
|
35
|
+
* can also be used standalone if you want to have a direct connection to the Realtime API.
|
|
36
|
+
*/
|
|
37
|
+
export declare class OpenAIRealtimeWebSocket extends OpenAIRealtimeBase implements RealtimeTransportLayer {
|
|
38
|
+
#private;
|
|
39
|
+
/**
|
|
40
|
+
* Timestamp maintained by the transport layer to aid with the calculation of the elapsed time
|
|
41
|
+
* since the response started to compute the right interruption time.
|
|
42
|
+
*
|
|
43
|
+
* Mostly internal but might be used by extended transport layers for their interruption
|
|
44
|
+
* calculation.
|
|
45
|
+
*/
|
|
46
|
+
protected _firstAudioTimestamp: number | undefined;
|
|
47
|
+
protected _audioLengthMs: number;
|
|
48
|
+
constructor(options?: OpenAIRealtimeWebSocketOptions);
|
|
49
|
+
/**
|
|
50
|
+
* The current status of the WebSocket connection.
|
|
51
|
+
*/
|
|
52
|
+
get status(): "connecting" | "connected" | "disconnected";
|
|
53
|
+
/**
|
|
54
|
+
* The current connection state of the WebSocket connection.
|
|
55
|
+
*/
|
|
56
|
+
get connectionState(): WebSocketState;
|
|
57
|
+
/**
|
|
58
|
+
* Always returns `null` as the WebSocket transport layer does not handle muting. Instead,
|
|
59
|
+
* this should be handled by the client by not triggering the `sendAudio` method.
|
|
60
|
+
*/
|
|
61
|
+
get muted(): null;
|
|
62
|
+
/**
|
|
63
|
+
* The current item ID of the ongoing response.
|
|
64
|
+
*/
|
|
65
|
+
protected get currentItemId(): string | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Triggers the `audio` event that a client might listen to to receive the audio buffer.
|
|
68
|
+
* Protected for you to be able to override and disable emitting this event in case your extended
|
|
69
|
+
* transport layer handles audio internally.
|
|
70
|
+
*
|
|
71
|
+
* @param audioEvent - The audio event to emit.
|
|
72
|
+
*/
|
|
73
|
+
protected _onAudio(audioEvent: TransportLayerAudio): void;
|
|
74
|
+
connect(options: RealtimeTransportLayerConnectOptions): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Send an event to the Realtime API. This will stringify the event and send it directly to the
|
|
77
|
+
* API. This can be used if you want to take control over the connection and send events manually.
|
|
78
|
+
*
|
|
79
|
+
* @param event - The event to send.
|
|
80
|
+
*/
|
|
81
|
+
sendEvent(event: RealtimeClientMessage): void;
|
|
82
|
+
/**
|
|
83
|
+
* Close the WebSocket connection.
|
|
84
|
+
*
|
|
85
|
+
* This will also reset any internal connection tracking used for interruption handling.
|
|
86
|
+
*/
|
|
87
|
+
close(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Will throw an error as the WebSocket transport layer does not support muting.
|
|
90
|
+
*/
|
|
91
|
+
mute(_muted: boolean): never;
|
|
92
|
+
/**
|
|
93
|
+
* Send an audio buffer to the Realtime API. This is used for your client to send audio to the
|
|
94
|
+
* model to respond.
|
|
95
|
+
*
|
|
96
|
+
* @param audio - The audio buffer to send.
|
|
97
|
+
* @param options - The options for the audio buffer.
|
|
98
|
+
*/
|
|
99
|
+
sendAudio(audio: ArrayBuffer, options?: {
|
|
100
|
+
commit?: boolean;
|
|
101
|
+
}): void;
|
|
102
|
+
/**
|
|
103
|
+
* Send a cancel response event to the Realtime API. This is used to cancel an ongoing
|
|
104
|
+
* response that the model is currently generating.
|
|
105
|
+
*/
|
|
106
|
+
_cancelResponse(): void;
|
|
107
|
+
/**
|
|
108
|
+
* Do NOT call this method directly. Call `interrupt()` instead for proper interruption handling.
|
|
109
|
+
*
|
|
110
|
+
* This method is used to send the right events to the API to inform the model that the user has
|
|
111
|
+
* interrupted the response. It might be overridden/extended by an extended transport layer. See
|
|
112
|
+
* the `TwilioRealtimeTransportLayer` for an example.
|
|
113
|
+
*
|
|
114
|
+
* @param elapsedTime - The elapsed time since the response started.
|
|
115
|
+
*/
|
|
116
|
+
_interrupt(elapsedTime: number): void;
|
|
117
|
+
/**
|
|
118
|
+
* Interrupt the ongoing response. This method is triggered automatically by the client when
|
|
119
|
+
* voice activity detection (VAD) is enabled (default) as well as when an output guardrail got
|
|
120
|
+
* triggered.
|
|
121
|
+
*
|
|
122
|
+
* You can also call this method directly if you want to interrupt the conversation for example
|
|
123
|
+
* based on an event in the client.
|
|
124
|
+
*/
|
|
125
|
+
interrupt(): void;
|
|
126
|
+
}
|