@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.
252 lines • 8.38 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WEBSOCKET_META = exports.HEADERS = void 0;
exports.base64ToArrayBuffer = base64ToArrayBuffer;
exports.arrayBufferToBase64 = arrayBufferToBase64;
exports.getLastTextFromAudioOutputMessage = getLastTextFromAudioOutputMessage;
exports.diffRealtimeHistory = diffRealtimeHistory;
exports.hasWebRTCSupport = hasWebRTCSupport;
exports.removeAudioFromContent = removeAudioFromContent;
exports.updateRealtimeHistory = updateRealtimeHistory;
exports.realtimeApprovalItemToApprovalItem = realtimeApprovalItemToApprovalItem;
exports.approvalItemToRealtimeApprovalItem = approvalItemToRealtimeApprovalItem;
const metadata_1 = __importDefault(require("./metadata.js"));
const agents_core_1 = require("@openai/agents-core");
/**
* Converts a base64 string to an ArrayBuffer
* @param {string} base64
* @returns {ArrayBuffer}
*/
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}
*/
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
*/
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 === 'output_text') {
return typeof lastContentItem.text === 'string'
? lastContentItem.text
: undefined;
}
if (lastContentItem.type === 'output_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.
*/
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.
*/
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
*/
function removeAudioFromContent(item) {
if (item.role === 'system') {
return item;
}
if (item.role === 'assistant') {
return {
...item,
content: item.content.map((entry) => {
if (entry.type === 'output_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.
*/
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.
*/
exports.HEADERS = {
'User-Agent': `Agents/JavaScript ${metadata_1.default.version}`,
'X-OpenAI-Agents-SDK': `openai-agents-sdk.${metadata_1.default.version}`,
};
/**
* Browser websocket header
*/
exports.WEBSOCKET_META = `openai-agents-sdk.${metadata_1.default.version}`;
function realtimeApprovalItemToApprovalItem(agent, item) {
const { name, arguments: args, ...rest } = item;
return new agents_core_1.RunToolApprovalItem({
type: 'hosted_tool_call',
name,
arguments: JSON.stringify(args),
status: 'in_progress',
providerData: {
...rest,
},
}, agent);
}
function approvalItemToRealtimeApprovalItem(item) {
const { name, arguments: args, providerData } = item.rawItem;
const { itemId, serverLabel, ...rest } = providerData ?? {};
if (!itemId || !serverLabel) {
throw new Error('Invalid approval item for Realtime MCP approval request');
}
return {
type: 'mcp_approval_request',
itemId,
serverLabel,
...rest,
name,
arguments: args ? JSON.parse(args) : {},
approved: null,
};
}
//# sourceMappingURL=utils.js.map