Skip to content

Commit 58ca906

Browse files
committed
Typed events
1 parent 339e955 commit 58ca906

File tree

5 files changed

+396
-34
lines changed

5 files changed

+396
-34
lines changed

lib/api.js

+3-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ export class RealtimeAPI extends RealtimeEventHandler {
55
/**
66
* Create a new RealtimeAPI instance
77
* @param {{url?: string, apiKey?: string, dangerouslyAllowAPIKeyInBrowser?: boolean, debug?: boolean}} [settings]
8-
* @returns {RealtimeAPI}
98
*/
109
constructor({ url, apiKey, dangerouslyAllowAPIKeyInBrowser, debug } = {}) {
1110
super();
@@ -14,6 +13,7 @@ export class RealtimeAPI extends RealtimeEventHandler {
1413
this.apiKey = apiKey || null;
1514
this.debug = !!debug;
1615
this.ws = null;
16+
1717
if (globalThis.document && this.apiKey) {
1818
if (!dangerouslyAllowAPIKeyInBrowser) {
1919
throw new Error(
@@ -156,7 +156,7 @@ export class RealtimeAPI extends RealtimeEventHandler {
156156

157157
/**
158158
* Disconnects from Realtime API server
159-
* @param {WebSocket} [ws]
159+
* @param {typeof globalThis.WebSocket} [ws]
160160
* @returns {true}
161161
*/
162162
disconnect(ws) {
@@ -182,15 +182,12 @@ export class RealtimeAPI extends RealtimeEventHandler {
182182

183183
/**
184184
* Sends an event to WebSocket and dispatches as "client.{eventName}" and "client.*" events
185-
* @param {string} eventName
186-
* @param {{[key: string]: any}} event
187-
* @returns {true}
185+
* @type {import('./types').SendEvent}
188186
*/
189187
send(eventName, data) {
190188
if (!this.isConnected()) {
191189
throw new Error(`RealtimeAPI is not connected`);
192190
}
193-
data = data || {};
194191
if (typeof data !== 'object') {
195192
throw new Error(`data must be an object`);
196193
}

lib/client.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import { RealtimeUtils } from './utils.js';
5959
/**
6060
* @typedef {Object} InputAudioContentType
6161
* @property {"input_audio"} type
62-
* @property {string} [audio] base64-encoded audio data
62+
* @property {string|ArrayBuffer|Int16Array} [audio] base64-encoded audio data
6363
* @property {string|null} [transcript]
6464
*/
6565

@@ -193,6 +193,7 @@ export class RealtimeClient extends RealtimeEventHandler {
193193
*/
194194
constructor({ url, apiKey, dangerouslyAllowAPIKeyInBrowser, debug } = {}) {
195195
super();
196+
/* @type { import('./types').SessionConfig }*/
196197
this.defaultSessionConfig = {
197198
modalities: ['text', 'audio'],
198199
instructions: '',
@@ -295,6 +296,7 @@ export class RealtimeClient extends RealtimeEventHandler {
295296
throw new Error(`Tool "${tool.name}" has not been added`);
296297
}
297298
const result = await toolConfig.handler(jsonArguments);
299+
298300
this.realtime.send('conversation.item.create', {
299301
item: {
300302
type: 'function_call_output',
@@ -344,6 +346,7 @@ export class RealtimeClient extends RealtimeEventHandler {
344346
'server.response.audio_transcript.delta',
345347
handlerWithDispatch,
346348
);
349+
347350
this.realtime.on('server.response.audio.delta', handlerWithDispatch);
348351
this.realtime.on('server.response.text.delta', handlerWithDispatch);
349352
this.realtime.on(
@@ -533,7 +536,7 @@ export class RealtimeClient extends RealtimeEventHandler {
533536
};
534537
}),
535538
);
536-
const session = { ...this.sessionConfig };
539+
const session = { ...this.sessionConfig, tools: useTools };
537540
session.tools = useTools;
538541
if (this.realtime.isConnected()) {
539542
this.realtime.send('session.update', { session });
@@ -559,6 +562,7 @@ export class RealtimeClient extends RealtimeEventHandler {
559562
item: {
560563
type: 'message',
561564
role: 'user',
565+
//@ts-ignore TODO fix
562566
content,
563567
},
564568
});
@@ -594,11 +598,11 @@ export class RealtimeClient extends RealtimeEventHandler {
594598
this.getTurnDetectionType() === null &&
595599
this.inputAudioBuffer.byteLength > 0
596600
) {
597-
this.realtime.send('input_audio_buffer.commit');
601+
this.realtime.send('input_audio_buffer.commit', null);
598602
this.conversation.queueInputAudio(this.inputAudioBuffer);
599603
this.inputAudioBuffer = new Int16Array(0);
600604
}
601-
this.realtime.send('response.create');
605+
this.realtime.send('response.create', null);
602606
return true;
603607
}
604608

@@ -611,7 +615,7 @@ export class RealtimeClient extends RealtimeEventHandler {
611615
*/
612616
cancelResponse(id, sampleCount = 0) {
613617
if (!id) {
614-
this.realtime.send('response.cancel');
618+
this.realtime.send('response.cancel', null);
615619
return { item: null };
616620
} else if (id) {
617621
const item = this.conversation.getItem(id);
@@ -625,7 +629,7 @@ export class RealtimeClient extends RealtimeEventHandler {
625629
`Can only cancelResponse messages with role "assistant"`,
626630
);
627631
}
628-
this.realtime.send('response.cancel');
632+
this.realtime.send('response.cancel', null);
629633
const audioIndex = item.content.findIndex((c) => c.type === 'audio');
630634
if (audioIndex === -1) {
631635
throw new Error(`Could not find audio on item to cancel`);
@@ -643,7 +647,6 @@ export class RealtimeClient extends RealtimeEventHandler {
643647

644648
/**
645649
* Utility for waiting for the next `conversation.item.appended` event to be triggered by the server
646-
* @returns {Promise<{item: ItemType}>}
647650
*/
648651
async waitForNextItem() {
649652
const event = await this.waitForNext('conversation.item.appended');
@@ -653,7 +656,6 @@ export class RealtimeClient extends RealtimeEventHandler {
653656

654657
/**
655658
* Utility for waiting for the next `conversation.item.completed` event to be triggered by the server
656-
* @returns {Promise<{item: ItemType}>}
657659
*/
658660
async waitForNextCompletedItem() {
659661
const event = await this.waitForNext('conversation.item.completed');

lib/event_handler.js

+15-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
/**
2-
* EventHandler callback
3-
* @typedef {(event: {[key: string]: any}): void} EventHandlerCallbackType
2+
* @typedef {import('./types').Listener} Listener
3+
* @typedef {import('./types').ListenerBool} ListenerBool
4+
* @typedef {import('./types').WaitForNext} WaitForNext
5+
* @typedef {import('./types').EventNames} EventNames
6+
* @typedef {Object.<EventNames, Listener[]>} EventHandlers
7+
48
*/
59

610
const sleep = (t) => new Promise((r) => setTimeout(() => r(), t));
@@ -13,10 +17,11 @@ const sleep = (t) => new Promise((r) => setTimeout(() => r(), t));
1317
export class RealtimeEventHandler {
1418
/**
1519
* Create a new RealtimeEventHandler instance
16-
* @returns {RealtimeEventHandler}
1720
*/
1821
constructor() {
22+
/** @type {EventHandlers} */
1923
this.eventHandlers = {};
24+
/** @type {EventHandlers} */
2025
this.nextEventHandlers = {};
2126
}
2227

@@ -30,11 +35,9 @@ export class RealtimeEventHandler {
3035
return true;
3136
}
3237

33-
/**
34-
* Listen to specific events
35-
* @param {string} eventName The name of the event to listen to
36-
* @param {EventHandlerCallbackType} callback Code to execute on event
37-
* @returns {EventHandlerCallbackType}
38+
/**
39+
* Register an event listener
40+
* @type {Listener}
3841
*/
3942
on(eventName, callback) {
4043
this.eventHandlers[eventName] = this.eventHandlers[eventName] || [];
@@ -44,9 +47,7 @@ export class RealtimeEventHandler {
4447

4548
/**
4649
* Listen for the next event of a specified type
47-
* @param {string} eventName The name of the event to listen to
48-
* @param {EventHandlerCallbackType} callback Code to execute on event
49-
* @returns {EventHandlerCallbackType}
50+
* @type {Listener}
5051
*/
5152
onNext(eventName, callback) {
5253
this.nextEventHandlers[eventName] = this.nextEventHandlers[eventName] || [];
@@ -57,9 +58,7 @@ export class RealtimeEventHandler {
5758
/**
5859
* Turns off event listening for specific events
5960
* Calling without a callback will remove all listeners for the event
60-
* @param {string} eventName
61-
* @param {EventHandlerCallbackType} [callback]
62-
* @returns {true}
61+
* @type {ListenerBool}
6362
*/
6463
off(eventName, callback) {
6564
const handlers = this.eventHandlers[eventName] || [];
@@ -80,9 +79,7 @@ export class RealtimeEventHandler {
8079
/**
8180
* Turns off event listening for the next event of a specific type
8281
* Calling without a callback will remove all listeners for the next event
83-
* @param {string} eventName
84-
* @param {EventHandlerCallbackType} [callback]
85-
* @returns {true}
82+
* @type {ListenerBool}
8683
*/
8784
offNext(eventName, callback) {
8885
const nextHandlers = this.nextEventHandlers[eventName] || [];
@@ -102,9 +99,7 @@ export class RealtimeEventHandler {
10299

103100
/**
104101
* Waits for next event of a specific type and returns the payload
105-
* @param {string} eventName
106-
* @param {number|null} [timeout]
107-
* @returns {Promise<{[key: string]: any}|null>}
102+
* @type {WaitForNext}
108103
*/
109104
async waitForNext(eventName, timeout = null) {
110105
const t0 = Date.now();

0 commit comments

Comments
 (0)