@inworld/web-core
Version:
358 lines (357 loc) • 13.7 kB
JavaScript
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;
}
}