-
Notifications
You must be signed in to change notification settings - Fork 276
Typed events #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Typed events #43
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ export class RealtimeAPI extends RealtimeEventHandler { | |
/** | ||
* Create a new RealtimeAPI instance | ||
* @param {{url?: string, apiKey?: string, dangerouslyAllowAPIKeyInBrowser?: boolean, debug?: boolean}} [settings] | ||
* @returns {RealtimeAPI} | ||
*/ | ||
constructor({ url, apiKey, dangerouslyAllowAPIKeyInBrowser, debug } = {}) { | ||
super(); | ||
|
@@ -14,6 +13,7 @@ export class RealtimeAPI extends RealtimeEventHandler { | |
this.apiKey = apiKey || null; | ||
this.debug = !!debug; | ||
this.ws = null; | ||
|
||
if (globalThis.document && this.apiKey) { | ||
if (!dangerouslyAllowAPIKeyInBrowser) { | ||
throw new Error( | ||
|
@@ -156,7 +156,7 @@ export class RealtimeAPI extends RealtimeEventHandler { | |
|
||
/** | ||
* Disconnects from Realtime API server | ||
* @param {WebSocket} [ws] | ||
* @param {typeof globalThis.WebSocket} [ws] | ||
* @returns {true} | ||
*/ | ||
disconnect(ws) { | ||
|
@@ -182,15 +182,12 @@ export class RealtimeAPI extends RealtimeEventHandler { | |
|
||
/** | ||
* Sends an event to WebSocket and dispatches as "client.{eventName}" and "client.*" events | ||
* @param {string} eventName | ||
* @param {{[key: string]: any}} event | ||
* @returns {true} | ||
* @type {import('./types').SendEvent} | ||
*/ | ||
send(eventName, data) { | ||
if (!this.isConnected()) { | ||
throw new Error(`RealtimeAPI is not connected`); | ||
} | ||
data = data || {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. redundant |
||
if (typeof data !== 'object') { | ||
throw new Error(`data must be an object`); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,7 +59,7 @@ import { RealtimeUtils } from './utils.js'; | |
/** | ||
* @typedef {Object} InputAudioContentType | ||
* @property {"input_audio"} type | ||
* @property {string} [audio] base64-encoded audio data | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. later we check if ArrayBuffer | Int16Array, and if so, we convert to base64. Here we ensure the type allows for ArrayBuffer | Int16Array |
||
* @property {string|ArrayBuffer|Int16Array} [audio] base64-encoded audio data | ||
* @property {string|null} [transcript] | ||
*/ | ||
|
||
|
@@ -118,6 +118,7 @@ import { RealtimeUtils } from './utils.js'; | |
* @property {string|null} [previous_item_id] | ||
* @property {"function_call_output"} type | ||
* @property {string} call_id | ||
* @property {string} status | ||
* @property {string} output | ||
*/ | ||
|
||
|
@@ -143,6 +144,7 @@ import { RealtimeUtils } from './utils.js'; | |
* @typedef {Object} FormattedItemType | ||
* @property {string} id | ||
* @property {string} object | ||
* @property {string} status | ||
* @property {"user"|"assistant"|"system"} [role] | ||
* @property {FormattedPropertyType} formatted | ||
*/ | ||
|
@@ -193,6 +195,7 @@ export class RealtimeClient extends RealtimeEventHandler { | |
*/ | ||
constructor({ url, apiKey, dangerouslyAllowAPIKeyInBrowser, debug } = {}) { | ||
super(); | ||
/* @type { import('./types').SessionConfig }*/ | ||
this.defaultSessionConfig = { | ||
modalities: ['text', 'audio'], | ||
instructions: '', | ||
|
@@ -295,6 +298,7 @@ export class RealtimeClient extends RealtimeEventHandler { | |
throw new Error(`Tool "${tool.name}" has not been added`); | ||
} | ||
const result = await toolConfig.handler(jsonArguments); | ||
|
||
this.realtime.send('conversation.item.create', { | ||
item: { | ||
type: 'function_call_output', | ||
|
@@ -344,6 +348,7 @@ export class RealtimeClient extends RealtimeEventHandler { | |
'server.response.audio_transcript.delta', | ||
handlerWithDispatch, | ||
); | ||
|
||
this.realtime.on('server.response.audio.delta', handlerWithDispatch); | ||
this.realtime.on('server.response.text.delta', handlerWithDispatch); | ||
this.realtime.on( | ||
|
@@ -533,7 +538,7 @@ export class RealtimeClient extends RealtimeEventHandler { | |
}; | ||
}), | ||
); | ||
const session = { ...this.sessionConfig }; | ||
const session = { ...this.sessionConfig, tools: useTools }; | ||
session.tools = useTools; | ||
if (this.realtime.isConnected()) { | ||
this.realtime.send('session.update', { session }); | ||
|
@@ -559,6 +564,7 @@ export class RealtimeClient extends RealtimeEventHandler { | |
item: { | ||
type: 'message', | ||
role: 'user', | ||
//@ts-ignore TODO fix | ||
content, | ||
}, | ||
}); | ||
|
@@ -594,11 +600,11 @@ export class RealtimeClient extends RealtimeEventHandler { | |
this.getTurnDetectionType() === null && | ||
this.inputAudioBuffer.byteLength > 0 | ||
) { | ||
this.realtime.send('input_audio_buffer.commit'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't find a way yet to skip the second argument if it's nullable |
||
this.realtime.send('input_audio_buffer.commit', null); | ||
this.conversation.queueInputAudio(this.inputAudioBuffer); | ||
this.inputAudioBuffer = new Int16Array(0); | ||
} | ||
this.realtime.send('response.create'); | ||
this.realtime.send('response.create', null); | ||
return true; | ||
} | ||
|
||
|
@@ -611,7 +617,7 @@ export class RealtimeClient extends RealtimeEventHandler { | |
*/ | ||
cancelResponse(id, sampleCount = 0) { | ||
if (!id) { | ||
this.realtime.send('response.cancel'); | ||
this.realtime.send('response.cancel', null); | ||
return { item: null }; | ||
} else if (id) { | ||
const item = this.conversation.getItem(id); | ||
|
@@ -625,7 +631,7 @@ export class RealtimeClient extends RealtimeEventHandler { | |
`Can only cancelResponse messages with role "assistant"`, | ||
); | ||
} | ||
this.realtime.send('response.cancel'); | ||
this.realtime.send('response.cancel', null); | ||
const audioIndex = item.content.findIndex((c) => c.type === 'audio'); | ||
if (audioIndex === -1) { | ||
throw new Error(`Could not find audio on item to cancel`); | ||
|
@@ -643,7 +649,6 @@ export class RealtimeClient extends RealtimeEventHandler { | |
|
||
/** | ||
* Utility for waiting for the next `conversation.item.appended` event to be triggered by the server | ||
* @returns {Promise<{item: ItemType}>} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The return type is implicit from |
||
*/ | ||
async waitForNextItem() { | ||
const event = await this.waitForNext('conversation.item.appended'); | ||
|
@@ -653,7 +658,6 @@ export class RealtimeClient extends RealtimeEventHandler { | |
|
||
/** | ||
* Utility for waiting for the next `conversation.item.completed` event to be triggered by the server | ||
* @returns {Promise<{item: ItemType}>} | ||
*/ | ||
async waitForNextCompletedItem() { | ||
const event = await this.waitForNext('conversation.item.completed'); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,8 @@ import { RealtimeUtils } from './utils.js'; | |
export class RealtimeConversation { | ||
defaultFrequency = 24_000; // 24,000 Hz | ||
|
||
EventProcessors = { | ||
/** @type { import('./types').EventProcessors} */ | ||
eventProcessors = { | ||
'conversation.item.created': (event) => { | ||
const { item } = event; | ||
// deep copy values | ||
|
@@ -240,7 +241,6 @@ export class RealtimeConversation { | |
|
||
/** | ||
* Create a new RealtimeConversation instance | ||
* @returns {RealtimeConversation} | ||
*/ | ||
constructor() { | ||
this.clear(); | ||
|
@@ -275,7 +275,7 @@ export class RealtimeConversation { | |
* Process an event from the WebSocket server and compose items | ||
* @param {Object} event | ||
* @param {...any} args | ||
* @returns {item: import('./client.js').ItemType | null, delta: ItemContentDeltaType | null} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a jsdoc error I think - we need double braces to define object |
||
* @returns {{item: import('./client.js').ItemType | null, delta: ItemContentDeltaType | null}} | ||
*/ | ||
processEvent(event, ...args) { | ||
if (!event.event_id) { | ||
|
@@ -286,7 +286,7 @@ export class RealtimeConversation { | |
console.error(event); | ||
throw new Error(`Missing "type" on event`); | ||
} | ||
const eventProcessor = this.EventProcessors[event.type]; | ||
const eventProcessor = this.eventProcessors[event.type]; | ||
if (!eventProcessor) { | ||
throw new Error( | ||
`Missing conversation event processor for "${event.type}"`, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think
returns
should be defined on the constructor