UNPKG

@openai/agents-realtime

Version:

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows. This package contains the logic for building realtime voice agents on the server or in the browser.

208 lines 6.63 kB
import METADATA from "./metadata.js"; /** * Converts a base64 string to an ArrayBuffer * @param {string} base64 * @returns {ArrayBuffer} */ export function base64ToArrayBuffer(base64) { const binaryString = atob(base64); const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; } /** * Converts an ArrayBuffer to a base64 string * @param {ArrayBuffer} arrayBuffer * @returns {string} */ export function arrayBufferToBase64(arrayBuffer) { const binaryString = String.fromCharCode(...new Uint8Array(arrayBuffer)); return btoa(binaryString); } /** * Get the last text from an audio output message * @param item * @returns */ export function getLastTextFromAudioOutputMessage(item) { if (typeof item === 'undefined' || item === null || typeof item !== 'object' || !('type' in item) || typeof item.type !== 'string' || !item.type) { return undefined; } if (item.type !== 'message') { return undefined; } if (!('content' in item) || !Array.isArray(item.content) || item.content.length < 1) { return undefined; } const lastContentItem = item.content[item.content.length - 1]; if (!('type' in lastContentItem) || typeof lastContentItem.type !== 'string') { return undefined; } if (lastContentItem.type === 'text') { return typeof lastContentItem.text === 'string' ? lastContentItem.text : undefined; } if (lastContentItem.type === 'audio') { return typeof lastContentItem.transcript === 'string' ? lastContentItem.transcript : undefined; } return undefined; } /** * Compare two conversation histories to determine the removals, additions, and updates. * @param oldHistory - The old history. * @param newHistory - The new history. * @returns A diff of the two histories. */ export function diffRealtimeHistory(oldHistory, newHistory) { const removals = oldHistory.filter((item) => !newHistory.some((newItem) => newItem.itemId === item.itemId)); const additions = newHistory.filter((item) => !oldHistory.some((oldItem) => oldItem.itemId === item.itemId)); const updates = newHistory.filter((item) => oldHistory.some((oldItem) => oldItem.itemId === item.itemId && JSON.stringify(oldItem) !== JSON.stringify(item))); return { removals, additions, updates, }; } /** * Check if the browser supports WebRTC. * @returns True if WebRTC is supported, false otherwise. */ export function hasWebRTCSupport() { if (typeof window === 'undefined') { return false; } return typeof window['RTCPeerConnection'] !== 'undefined'; } /** * Removes the audio data from all content in a message by setting it to null. * @param item * @returns */ export function removeAudioFromContent(item) { if (item.role === 'system') { return item; } if (item.role === 'assistant') { return { ...item, content: item.content.map((entry) => { if (entry.type === 'audio') { return { ...entry, audio: null, }; } return entry; }), }; } if (item.role === 'user') { return { ...item, content: item.content.map((entry) => { if (entry.type === 'input_audio') { return { ...entry, audio: null, }; } return entry; }), }; } return item; } /** * Updates the realtime history array based on the incoming event and options. * @param history - The current history array. * @param event - The event to process (RealtimeItem). * @param shouldIncludeAudioData - Whether to include audio data in message items. * @returns The updated history array. */ export function updateRealtimeHistory(history, event, shouldIncludeAudioData) { // Merge transcript into placeholder input_audio message if (event.type === 'conversation.item.input_audio_transcription.completed') { return history.map((item) => { if (item.itemId === event.item_id && item.type === 'message' && 'role' in item && item.role === 'user') { const updatedContent = item.content.map((entry) => { if (entry.type === 'input_audio') { return { ...entry, transcript: event.transcript, }; } return entry; }); return { ...item, content: updatedContent, status: 'completed', }; } return item; }); } const newEvent = !shouldIncludeAudioData && event.type === 'message' ? removeAudioFromContent(event) : event; const existingIndex = history.findIndex((item) => item.itemId === event.itemId); if (existingIndex !== -1) { // Update existing item return history.map((item, idx) => { if (idx === existingIndex) { return newEvent; } if (!shouldIncludeAudioData && item.type === 'message') { return removeAudioFromContent(item); } return item; }); } else if (event.previousItemId) { // Insert after previousItemId if found, else at end const prevIndex = history.findIndex((item) => item.itemId === event.previousItemId); if (prevIndex !== -1) { return [ ...history.slice(0, prevIndex + 1), newEvent, ...history.slice(prevIndex + 1), ]; } else { return [...history, newEvent]; } } else { return [...history, newEvent]; } } /** * The headers to use for the Realtime API. */ export const HEADERS = { 'User-Agent': `Agents/JavaScript ${METADATA.version}`, 'X-OpenAI-Agents-SDK': `openai-agents-sdk.${METADATA.version}`, }; /** * Browser websocket header */ export const WEBSOCKET_META = `openai-agents-sdk.${METADATA.version}`; //# sourceMappingURL=utils.js.map