-
Notifications
You must be signed in to change notification settings - Fork 276
/
Copy pathapi.js
142 lines (134 loc) · 4.32 KB
/
api.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { RealtimeEventHandler } from './event_handler.js';
import { RealtimeUtils } from './utils.js';
import { RealtimeTransportType } from './transport.js';
import { RealtimeTransportWebRTC } from './transport_webrtc.js';
import { RealtimeTransportWebSocket } from './transport_websocket.js';
export class RealtimeAPI extends RealtimeEventHandler {
/**
* Create a new RealtimeAPI instance
* @param {{transportType?: string, url?: string, apiKey?: string, dangerouslyAllowAPIKeyInBrowser?: boolean, debug?: boolean}} [settings]
* @returns {RealtimeAPI}
*/
constructor({ transportType, url, apiKey, dangerouslyAllowAPIKeyInBrowser, debug } = {}) {
super();
this.debug = !!debug;
transportType = transportType?.toUpperCase() || RealtimeTransportType.WEBRTC;
switch (transportType) {
case RealtimeTransportType.WEBRTC: {
this.transport = new RealtimeTransportWebRTC({ url, apiKey, dangerouslyAllowAPIKeyInBrowser, debug });
break;
}
case RealtimeTransportType.WEBSOCKET: {
this.transport = new RealtimeTransportWebSocket({ url, apiKey, dangerouslyAllowAPIKeyInBrowser, debug });
break;
}
default: {
throw new Error(`Invalid transportType: "${transportType}"`);
}
}
this.transport.on('close', (data) => {
this.disconnect();
this.dispatch('close', data);
});
this.transport.on('message', (event) => {
const message = JSON.parse(event.data);
this._receive(message.type, message)
});
}
get transportType() {
return this.transport.transportType;
}
/**
* Tells us whether or not the Realtime API server is connected
* @returns {boolean}
*/
get isConnected() {
return this.transport.isConnected;
}
/**
* Writes log to console
* @param {...any} args
* @returns {true}
*/
log(...args) {
const date = new Date().toISOString();
const logs = [`[RealtimeAPI/${date}]`].concat(args).map((arg) => {
if (typeof arg === 'object' && arg !== null) {
return JSON.stringify(arg, null, 2);
} else {
return arg;
}
});
if (this.debug) {
console.log(...logs);
}
return true;
}
/**
* Connects to Realtime API Server
* @param {{sessionConfig?: SessionConfig, setAudioOutputCallback?: Function, getMicrophoneCallback?: Function}} [settings]
* @returns {Promise<true>}
*/
async connect({ sessionConfig, setAudioOutputCallback, getMicrophoneCallback }) {
return this.transport.connect({ sessionConfig, setAudioOutputCallback, getMicrophoneCallback });
}
/**
* Disconnects from Realtime API server
* @returns {true}
*/
async disconnect() {
await this.transport.disconnect();
return true;
}
/**
* Receives an event from Realtime API server and dispatches as "server.{eventName}" and "server.*" events
* @param {string} eventName
* @param {{[key: string]: any}} event
* @returns {true}
*/
_receive(eventName, event) {
if (this.debug) {
if (eventName === 'response.audio.delta') {
const delta = event.delta;
this.log(`received:`, eventName, { ...event, delta: delta.slice(0, 10) + '...' + delta.slice(-10) });
} else {
this.log(`received:`, eventName, event);
}
}
this.dispatch(`server.${eventName}`, event);
this.dispatch('server.*', event);
return true;
}
/**
* Sends an event to Realtime API server and dispatches as "client.{eventName}" and "client.*" events
* @param {string} eventName
* @param {{[key: string]: any}} event
* @returns {true}
*/
async send(eventName, data) {
if (!this.isConnected) {
throw new Error(`RealtimeAPI is not connected`);
}
data = data || {};
if (typeof data !== 'object') {
throw new Error(`data must be an object`);
}
const event = {
event_id: RealtimeUtils.generateId('evt_'),
type: eventName,
...data,
};
this.dispatch(`client.${eventName}`, event);
this.dispatch('client.*', event);
if (this.debug) {
if (eventName === 'input_audio_buffer.append') {
const audio = event.audio;
this.log(`sending:`, eventName, { ...event, audio: audio.slice(0, 10) + '...' + audio.slice(-10) });
} else {
this.log(`sending:`, eventName, event);
}
}
await this.transport.send(event);
return true;
}
}