UNPKG

@inworld/web-core

Version:
358 lines (357 loc) 13.7 kB
import * as snakecaseKeys from 'snakecase-keys'; import { v4 } from 'uuid'; import { ActorType, AudioSessionStartPayloadMicrophoneMode, AudioSessionStartPayloadUnderstandingMode, ControlEventAction, CustomEventType, PerceivedLatencyReportPrecision, PingPongReportType, TextEventSourceType, } from '../../proto/ai/inworld/packets/packets.pb.js'; import { ConversationParticipant, MicrophoneMode, UnderstandingMode, } from '../common/data_structures/index.js'; import { calculateTimeDifference, protoTimestamp } from '../common/helpers.js'; import { ItemOperation } from '../entities/entities/item_operation.js'; import { PerceivedLatencyReport } from '../entities/packets/latency/perceived_latency_report.entity.js'; import { InworldPacketSchema } from '../zod/schema.js'; export class EventFactory { constructor({ validateData = false } = {}) { this.character = undefined; this.characters = []; this.validateData = validateData; } getCurrentCharacter() { return this.character; } setCurrentCharacter(character) { this.character = character; } setCharacters(characters) { this.characters = characters; } getCharacters() { return this.characters; } dataChunk(chunk, type, params) { const packet = Object.assign(Object.assign({}, this.baseProtoPacket({ utteranceId: false, interactionId: false, conversationId: params.conversationId, })), { dataChunk: { chunk: chunk, type } }); this.validate(packet); return packet; } audioSessionStart(params) { const packet = this.audioSession(ControlEventAction.AUDIO_SESSION_START, params); this.validate(packet); return packet; } audioSessionEnd(params) { const packet = this.audioSession(ControlEventAction.AUDIO_SESSION_END, params); this.validate(packet); return packet; } pong(packetId, pingTimestamp) { const packet = Object.assign(Object.assign({}, this.baseProtoPacket({ utteranceId: false, interactionId: false, })), { latencyReport: { pingPong: { pingPacketId: Object.assign({}, packetId), pingTimestamp, type: PingPongReportType.PONG, }, } }); this.validate(packet); return packet; } perceivedLatencyWithTypeDetection({ sent, received, }) { const duration = calculateTimeDifference(new Date(sent.date), new Date()); let precision = PerceivedLatencyReportPrecision.UNSPECIFIED; if (sent.isAudioSessionEnd()) { precision = PerceivedLatencyReportPrecision.PUSH_TO_TALK; } else if ((sent.isPlayerTypeInText() || sent.isSpeechRecognitionResult()) && received.isAudio()) { precision = PerceivedLatencyReportPrecision.ESTIMATED; } else if (sent.isNonSpeechPacket() || sent.isPlayerTypeInText()) { precision = PerceivedLatencyReportPrecision.NON_SPEECH; } const event = { perceivedLatency: { latency: duration, precision, }, }; const basePacket = this.baseProtoPacket({ utteranceId: false, interactionId: false, }); const basePacketId = basePacket.packetId; const packet = Object.assign(Object.assign({}, basePacket), { packetId: Object.assign(Object.assign({}, basePacketId), { interactionId: received.packetId.interactionId }), latencyReport: event }); this.validate(packet); return packet; } perceivedLatency({ precision, interactionId, startDate, endDate, }) { const duration = calculateTimeDifference(startDate, endDate); const event = { perceivedLatency: { latency: duration, precision: PerceivedLatencyReport.getProtoPerceivedLatencyReportPrecision(precision), }, }; const basePacket = this.baseProtoPacket({ utteranceId: false, interactionId: false, }); const basePacketId = basePacket.packetId; const packet = Object.assign(Object.assign({}, basePacket), { packetId: Object.assign(Object.assign({}, basePacketId), { interactionId }), latencyReport: event }); this.validate(packet); return packet; } mutePlayback(isMuted, params) { const packet = Object.assign(Object.assign({}, this.baseProtoPacket({ utteranceId: false, interactionId: false, conversationId: params.conversationId, })), { control: { action: isMuted ? ControlEventAction.TTS_PLAYBACK_MUTE : ControlEventAction.TTS_PLAYBACK_UNMUTE, } }); this.validate(packet); return packet; } text(text, params) { const packet = Object.assign(Object.assign({}, this.baseProtoPacket({ correlationId: true, conversationId: params.conversationId, })), { text: { sourceType: TextEventSourceType.TYPED_IN, text, final: true, } }); this.validate(packet); return packet; } trigger(name, params) { const packet = this.customEvent(name, CustomEventType.TRIGGER, params); this.validate(packet); return packet; } cancelResponse(params) { const packet = Object.assign(Object.assign({}, this.baseProtoPacket({ utteranceId: false, interactionId: false, correlationId: true, })), { mutation: { cancelResponses: { interactionId: params.interactionId, utteranceId: params.utteranceId, }, }, routing: this.routing({ target: { type: ActorType.WORLD }, }) }); this.validate(packet); return packet; } narratedAction(content, params) { const packet = Object.assign(Object.assign({}, this.baseProtoPacket({ correlationId: true, conversationId: params.conversationId, })), { action: { narratedAction: { content, }, } }); this.validate(packet); return packet; } conversation(participants, params) { const control = { action: ControlEventAction.CONVERSATION_UPDATE, conversationUpdate: { participants: participants.map((p) => p === ConversationParticipant.USER ? { type: ActorType.PLAYER, } : { name: p, type: ActorType.AGENT, }), }, }; const packet = { packetId: { packetId: v4(), conversationId: params.conversationId, }, timestamp: protoTimestamp(), control, }; this.validate(packet); return packet; } sessionControl(props) { const sessionConfiguration = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (!!props.capabilities && { capabilitiesConfiguration: props.capabilities, })), (!!props.sessionConfiguration && { sessionConfiguration: props.sessionConfiguration, })), (!!props.clientConfiguration && { clientConfiguration: props.clientConfiguration, })), (!!props.userConfiguration && { userConfiguration: props.userConfiguration, })), (!!props.continuation && { continuation: props.continuation })); const packet = Object.assign({ packetId: { packetId: v4(), }, timestamp: protoTimestamp(), routing: EventFactory.worldRouting() }, (Object.keys(sessionConfiguration).length ? { control: { action: ControlEventAction.SESSION_CONFIGURATION, sessionConfiguration, }, } : Object.assign({}, (props.sessionHistory && { sessionControl: { sessionHistoryRequest: props.sessionHistory }, })))); this.validate(packet); return packet; } loadScene(name) { const mutation = { loadScene: { name } }; const packet = { packetId: { packetId: v4(), interactionId: v4(), }, timestamp: protoTimestamp(), routing: EventFactory.worldRouting(), mutation, }; this.validate(packet); return packet; } loadCharacters(names) { const name = names.map((name) => ({ name, })); const mutation = { loadCharacters: { name } }; const packet = { packetId: { packetId: v4() }, timestamp: protoTimestamp(), routing: EventFactory.worldRouting(), mutation, }; this.validate(packet); return packet; } unloadCharacters(ids) { const agents = ids.map((agentId) => ({ agentId })); const mutation = { unloadCharacters: { agents } }; const packet = { packetId: { packetId: v4() }, timestamp: protoTimestamp(), routing: EventFactory.worldRouting(), mutation, }; this.validate(packet); return packet; } baseProtoPacket({ utteranceId = true, interactionId = true, correlationId, conversationId, } = {}) { return { packetId: Object.assign(Object.assign(Object.assign(Object.assign({ packetId: v4() }, (utteranceId && { utteranceId: v4() })), (interactionId && { interactionId: v4() })), (correlationId && { correlationId: v4() })), (conversationId && { conversationId })), timestamp: protoTimestamp(), routing: this.routing(), }; } audioSession(action, params) { let mode; let understandingMode; if (action === ControlEventAction.AUDIO_SESSION_START) { mode = params.mode === MicrophoneMode.EXPECT_AUDIO_END ? AudioSessionStartPayloadMicrophoneMode.EXPECT_AUDIO_END : AudioSessionStartPayloadMicrophoneMode.OPEN_MIC; understandingMode = params.understandingMode === UnderstandingMode.SPEECH_RECOGNITION_ONLY ? AudioSessionStartPayloadUnderstandingMode.SPEECH_RECOGNITION_ONLY : AudioSessionStartPayloadUnderstandingMode.FULL; } return Object.assign(Object.assign({}, this.baseProtoPacket({ utteranceId: false, interactionId: false, conversationId: params.conversationId, })), { control: Object.assign({ action }, ((mode || understandingMode) && { audioSessionStart: { mode, understandingMode }, })) }); } customEvent(name, type, params) { const { parameters = [], character, conversationId } = params; return Object.assign(Object.assign(Object.assign({}, this.baseProtoPacket({ correlationId: true, conversationId })), (character && { routing: this.routing({ target: { name: character.id, type: ActorType.AGENT }, }), })), { custom: { name, type, parameters: parameters.length ? parameters : undefined, } }); } routing(props) { return Object.assign({ source: { type: ActorType.PLAYER } }, ((props === null || props === void 0 ? void 0 : props.target) && { target: props.target })); } static worldRouting() { return { source: { type: ActorType.PLAYER }, target: { type: ActorType.WORLD }, }; } validate(packet) { if (!this.validateData) { return; } const inSnakeCase = snakecaseKeys.default(packet); const result = InworldPacketSchema.safeParse(inSnakeCase); if (!result.success) { console.warn('Invalid packet', { packet: inSnakeCase, errors: result.error.errors, }); } } createOrUpdateItems(props) { const packet = { packetId: { packetId: v4(), }, timestamp: protoTimestamp(), routing: EventFactory.worldRouting(), entitiesItemsOperation: new ItemOperation({ createOrUpdateItems: props, }).toProto(), }; this.validate(packet); return packet; } removeItems(ids) { const packet = { packetId: { packetId: v4(), }, timestamp: protoTimestamp(), routing: EventFactory.worldRouting(), entitiesItemsOperation: new ItemOperation({ removeItems: { itemIds: ids }, }).toProto(), }; this.validate(packet); return packet; } itemsInEntities(props) { const packet = { packetId: { packetId: v4(), }, timestamp: protoTimestamp(), routing: EventFactory.worldRouting(), entitiesItemsOperation: new ItemOperation({ itemsInEntities: props, }).toProto(), }; this.validate(packet); return packet; } }