diff --git a/src/modules/StreamingClient.ts b/src/modules/StreamingClient.ts index a668b34..83b1c26 100644 --- a/src/modules/StreamingClient.ts +++ b/src/modules/StreamingClient.ts @@ -1,29 +1,32 @@ +import { + ClientMetricMeasurement, + createRTCStatsReport, + sendClientMetric, +} from '../lib/ClientMetrics'; import { EngineApiRestClient, InternalEventEmitter, PublicEventEmitter, SignallingClient, + ToolCallManager, } from '../modules'; import { AnamEvent, - InputAudioState, + ApiGatewayConfig, AudioPermissionState, + ClientToolEvent, + ConnectionClosedCode, + DataChannelMessage, + InputAudioState, InternalEvent, SignalMessage, SignalMessageAction, StreamingClientOptions, + WebRtcClientToolEvent, WebRtcTextMessageEvent, - ConnectionClosedCode, - ApiGatewayConfig, - DataChannelMessage, } from '../types'; import { TalkMessageStream } from '../types/TalkMessageStream'; import { TalkStreamInterruptedSignalMessage } from '../types/signalling/TalkStreamInterruptedSignalMessage'; -import { - ClientMetricMeasurement, - createRTCStatsReport, - sendClientMetric, -} from '../lib/ClientMetrics'; const SUCCESS_METRIC_POLLING_TIMEOUT_MS = 15000; // After this time we will stop polling for the first frame and consider the session a failure. const STATS_COLLECTION_INTERVAL_MS = 5000; @@ -671,6 +674,22 @@ export class StreamingClient { message.data as WebRtcTextMessageEvent, ); break; + case DataChannelMessage.CLIENT_TOOL_EVENT: + const webRtcToolEvent = message.data as WebRtcClientToolEvent; + + this.internalEventEmitter.emit( + InternalEvent.WEBRTC_CLIENT_TOOL_EVENT_RECEIVED, + webRtcToolEvent, + ); + const clientToolEvent = + ToolCallManager.WebRTCClientToolEventToClientToolEvent( + webRtcToolEvent, + ); + this.publicEventEmitter.emit( + AnamEvent.CLIENT_TOOL_EVENT_RECEIVED, + clientToolEvent, + ); + break; // Unknown message types are silently ignored to maintain forward compatibility default: break; diff --git a/src/modules/ToolCallManager.ts b/src/modules/ToolCallManager.ts new file mode 100644 index 0000000..e1faf32 --- /dev/null +++ b/src/modules/ToolCallManager.ts @@ -0,0 +1,20 @@ +import { ClientToolEvent, WebRtcClientToolEvent } from '../types/streaming'; + +export class ToolCallManager { + /** + * Converts a WebRtcClientToolEvent to a ClientToolEvent + */ + static WebRTCClientToolEventToClientToolEvent( + webRtcEvent: WebRtcClientToolEvent, + ): ClientToolEvent { + return { + eventUid: webRtcEvent.event_uid, + sessionId: webRtcEvent.session_id, + eventName: webRtcEvent.event_name, + eventData: webRtcEvent.event_data, + timestamp: webRtcEvent.timestamp, + timestampUserAction: webRtcEvent.timestamp_user_action, + userActionCorrelationId: webRtcEvent.user_action_correlation_id, + }; + } +} diff --git a/src/modules/index.ts b/src/modules/index.ts index 08887f7..73698ac 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -5,3 +5,4 @@ export { InternalEventEmitter } from './InternalEventEmitter'; export { MessageHistoryClient } from './MessageHistoryClient'; export { PublicEventEmitter } from './PublicEventEmitter'; export { StreamingClient } from './StreamingClient'; +export { ToolCallManager } from './ToolCallManager'; diff --git a/src/types/events/internal/InternalEvent.ts b/src/types/events/internal/InternalEvent.ts index e7b58dc..bd677da 100644 --- a/src/types/events/internal/InternalEvent.ts +++ b/src/types/events/internal/InternalEvent.ts @@ -2,4 +2,5 @@ export enum InternalEvent { WEB_SOCKET_OPEN = 'WEB_SOCKET_OPEN', SIGNAL_MESSAGE_RECEIVED = 'SIGNAL_MESSAGE_RECEIVED', WEBRTC_CHAT_MESSAGE_RECEIVED = 'WEBRTC_CHAT_MESSAGE_RECEIVED', + WEBRTC_CLIENT_TOOL_EVENT_RECEIVED = 'WEBRTC_CLIENT_TOOL_EVENT_RECEIVED', } diff --git a/src/types/events/internal/InternalEventCallbacks.ts b/src/types/events/internal/InternalEventCallbacks.ts index 23b233a..5edbbad 100644 --- a/src/types/events/internal/InternalEventCallbacks.ts +++ b/src/types/events/internal/InternalEventCallbacks.ts @@ -2,6 +2,7 @@ import { InternalEvent, SignalMessage, WebRtcTextMessageEvent, + WebRtcClientToolEvent, } from '../../index'; export type InternalEventCallbacks = { @@ -12,4 +13,7 @@ export type InternalEventCallbacks = { [InternalEvent.WEBRTC_CHAT_MESSAGE_RECEIVED]: ( message: WebRtcTextMessageEvent, ) => void; + [InternalEvent.WEBRTC_CLIENT_TOOL_EVENT_RECEIVED]: ( + webRtcToolEvent: WebRtcClientToolEvent, + ) => void; }; diff --git a/src/types/events/public/AnamEvent.ts b/src/types/events/public/AnamEvent.ts index 5b4e00a..1d8e185 100644 --- a/src/types/events/public/AnamEvent.ts +++ b/src/types/events/public/AnamEvent.ts @@ -14,4 +14,5 @@ export enum AnamEvent { MIC_PERMISSION_GRANTED = 'MIC_PERMISSION_GRANTED', MIC_PERMISSION_DENIED = 'MIC_PERMISSION_DENIED', INPUT_AUDIO_DEVICE_CHANGED = 'INPUT_AUDIO_DEVICE_CHANGED', + CLIENT_TOOL_EVENT_RECEIVED = 'CLIENT_TOOL_EVENT_RECEIVED', } diff --git a/src/types/events/public/EventCallbacks.ts b/src/types/events/public/EventCallbacks.ts index beca017..dcd258f 100644 --- a/src/types/events/public/EventCallbacks.ts +++ b/src/types/events/public/EventCallbacks.ts @@ -1,5 +1,10 @@ import { ConnectionClosedCode } from './ConnectionClosedCodes'; -import { Message, MessageStreamEvent, AnamEvent } from '../../index'; +import { + Message, + MessageStreamEvent, + AnamEvent, + ClientToolEvent, +} from '../../index'; export type EventCallbacks = { [AnamEvent.MESSAGE_HISTORY_UPDATED]: (messages: Message[]) => void; @@ -22,4 +27,7 @@ export type EventCallbacks = { [AnamEvent.MIC_PERMISSION_GRANTED]: () => void; [AnamEvent.MIC_PERMISSION_DENIED]: (error: string) => void; [AnamEvent.INPUT_AUDIO_DEVICE_CHANGED]: (deviceId: string) => void; + [AnamEvent.CLIENT_TOOL_EVENT_RECEIVED]: ( + clientToolEvent: ClientToolEvent, + ) => void; }; diff --git a/src/types/streaming/ClientToolEvent.ts b/src/types/streaming/ClientToolEvent.ts new file mode 100644 index 0000000..aae22b8 --- /dev/null +++ b/src/types/streaming/ClientToolEvent.ts @@ -0,0 +1,12 @@ +export interface ClientToolEvent { + // Core event fields + eventUid: string; // Unique ID for this event + sessionId: string; // Session ID + eventName: string; // The tool name (e.g., "redirect") + eventData: Record; // LLM-generated parameters + + // Timing & correlation + timestamp: string; // ISO timestamp when event was created + timestampUserAction: string; // ISO timestamp of user action that triggered this + userActionCorrelationId: string; // Correlation ID for tracking +} diff --git a/src/types/streaming/DataChannelMessage.ts b/src/types/streaming/DataChannelMessage.ts index 8e3646e..7913f4b 100644 --- a/src/types/streaming/DataChannelMessage.ts +++ b/src/types/streaming/DataChannelMessage.ts @@ -1,3 +1,4 @@ export enum DataChannelMessage { SPEECH_TEXT = 'speechText', + CLIENT_TOOL_EVENT = 'clientToolEvent', } diff --git a/src/types/streaming/WebRtcClientToolEvent.ts b/src/types/streaming/WebRtcClientToolEvent.ts new file mode 100644 index 0000000..c5f56c8 --- /dev/null +++ b/src/types/streaming/WebRtcClientToolEvent.ts @@ -0,0 +1,15 @@ +export interface WebRtcClientToolEvent { + // Core event fields + event_uid: string; // Unique ID for this event + session_id: string; // Session ID + event_name: string; // The tool name (e.g., "redirect") + event_data: Record; // LLM-generated parameters + + // Timing & correlation + timestamp: string; // ISO timestamp when event was created + timestamp_user_action: string; // ISO timestamp of user action that triggered this + user_action_correlation_id: string; // Correlation ID for tracking + + // Metadata + used_outside_engine: boolean; // Always true for client events +} diff --git a/src/types/streaming/index.ts b/src/types/streaming/index.ts index d1fdb0b..2d383e2 100644 --- a/src/types/streaming/index.ts +++ b/src/types/streaming/index.ts @@ -1,4 +1,6 @@ export type { WebRtcTextMessageEvent } from './WebRtcTextMessageEvent'; +export type { ClientToolEvent } from './ClientToolEvent'; +export type { WebRtcClientToolEvent } from './WebRtcClientToolEvent'; export type { StreamingClientOptions } from './StreamingClientOptions'; export type { InputAudioOptions } from './InputAudioOptions'; export { DataChannelMessage } from './DataChannelMessage';