in lib/client.js [250:364]
_addAPIEventHandlers() {
// Event Logging handlers
this.realtime.on('client.*', (event) => {
const realtimeEvent = {
time: new Date().toISOString(),
source: 'client',
event: event,
};
this.dispatch('realtime.event', realtimeEvent);
});
this.realtime.on('server.*', (event) => {
const realtimeEvent = {
time: new Date().toISOString(),
source: 'server',
event: event,
};
this.dispatch('realtime.event', realtimeEvent);
});
// Handles session created event, can optionally wait for it
this.realtime.on(
'server.session.created',
() => (this.sessionCreated = true),
);
// Setup for application control flow
const handler = (event, ...args) => {
const { item, delta } = this.conversation.processEvent(event, ...args);
return { item, delta };
};
const handlerWithDispatch = (event, ...args) => {
const { item, delta } = handler(event, ...args);
if (item) {
// FIXME: If statement is only here because item.input_audio_transcription.completed
// can fire before `item.created`, resulting in empty item.
// This happens in VAD mode with empty audio
this.dispatch('conversation.updated', { item, delta });
}
return { item, delta };
};
const callTool = async (tool) => {
try {
const jsonArguments = JSON.parse(tool.arguments);
const toolConfig = this.tools[tool.name];
if (!toolConfig) {
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',
call_id: tool.call_id,
output: JSON.stringify(result),
},
});
} catch (e) {
this.realtime.send('conversation.item.create', {
item: {
type: 'function_call_output',
call_id: tool.call_id,
output: JSON.stringify({ error: e.message }),
},
});
}
this.createResponse();
};
// Handlers to update internal conversation state
this.realtime.on('server.response.created', handler);
this.realtime.on('server.response.output_item.added', handler);
this.realtime.on('server.response.content_part.added', handler);
this.realtime.on('server.input_audio_buffer.speech_started', (event) => {
handler(event);
this.dispatch('conversation.interrupted');
});
this.realtime.on('server.input_audio_buffer.speech_stopped', (event) =>
handler(event, this.inputAudioBuffer),
);
// Handlers to update application state
this.realtime.on('server.conversation.item.created', (event) => {
const { item } = handlerWithDispatch(event);
this.dispatch('conversation.item.appended', { item });
if (item.status === 'completed') {
this.dispatch('conversation.item.completed', { item });
}
});
this.realtime.on('server.conversation.item.truncated', handlerWithDispatch);
this.realtime.on('server.conversation.item.deleted', handlerWithDispatch);
this.realtime.on(
'server.conversation.item.input_audio_transcription.completed',
handlerWithDispatch,
);
this.realtime.on(
'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(
'server.response.function_call_arguments.delta',
handlerWithDispatch,
);
this.realtime.on('server.response.output_item.done', async (event) => {
const { item } = handlerWithDispatch(event);
if (item.status === 'completed') {
this.dispatch('conversation.item.completed', { item });
}
if (item.formatted.tool) {
callTool(item.formatted.tool);
}
});
return true;
}