206 lines
5.5 KiB
JavaScript
206 lines
5.5 KiB
JavaScript
|
|
const HEADER_SIZE_4 = 0x1;
|
||
|
|
const VERSION_1 = 0x10;
|
||
|
|
const SERIALIZATION_JSON = 0x1 << 4;
|
||
|
|
const SERIALIZATION_RAW = 0;
|
||
|
|
const COMPRESSION_NONE = 0;
|
||
|
|
const MSG_TYPE_FLAG_WITH_EVENT = 0b100;
|
||
|
|
|
||
|
|
const MsgType = {
|
||
|
|
INVALID: 0,
|
||
|
|
FULL_CLIENT: 1,
|
||
|
|
AUDIO_ONLY_CLIENT: 2,
|
||
|
|
FULL_SERVER: 9,
|
||
|
|
AUDIO_ONLY_SERVER: 11,
|
||
|
|
FRONT_END_RESULT_SERVER: 12,
|
||
|
|
ERROR: 15,
|
||
|
|
};
|
||
|
|
|
||
|
|
function getMessageTypeName(value) {
|
||
|
|
return Object.keys(MsgType).find((key) => MsgType[key] === value) || 'INVALID';
|
||
|
|
}
|
||
|
|
|
||
|
|
function containsEvent(typeFlag) {
|
||
|
|
return (typeFlag & MSG_TYPE_FLAG_WITH_EVENT) === MSG_TYPE_FLAG_WITH_EVENT;
|
||
|
|
}
|
||
|
|
|
||
|
|
function shouldHandleSessionId(event) {
|
||
|
|
return event !== 1 && event !== 2 && event !== 50 && event !== 51 && event !== 52;
|
||
|
|
}
|
||
|
|
|
||
|
|
function writeInt(buffer, value, offset) {
|
||
|
|
buffer.writeInt32BE(value, offset);
|
||
|
|
return offset + 4;
|
||
|
|
}
|
||
|
|
|
||
|
|
function writeStringWithLength(buffer, value, offset) {
|
||
|
|
const strBuffer = Buffer.from(value || '', 'utf8');
|
||
|
|
offset = writeInt(buffer, strBuffer.length, offset);
|
||
|
|
strBuffer.copy(buffer, offset);
|
||
|
|
return offset + strBuffer.length;
|
||
|
|
}
|
||
|
|
|
||
|
|
function writePayload(buffer, payload, offset) {
|
||
|
|
const payloadBuffer = Buffer.isBuffer(payload) ? payload : Buffer.from(payload || '');
|
||
|
|
offset = writeInt(buffer, payloadBuffer.length, offset);
|
||
|
|
payloadBuffer.copy(buffer, offset);
|
||
|
|
return offset + payloadBuffer.length;
|
||
|
|
}
|
||
|
|
|
||
|
|
function buildHeader(type, typeFlag, serialization) {
|
||
|
|
return Buffer.from([
|
||
|
|
VERSION_1 | HEADER_SIZE_4,
|
||
|
|
((type & 0x0f) << 4) | (typeFlag & 0x0f),
|
||
|
|
serialization | COMPRESSION_NONE,
|
||
|
|
0,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
function marshal(message, { rawPayload = false } = {}) {
|
||
|
|
const type = message.type;
|
||
|
|
const typeFlag = message.typeFlag || MSG_TYPE_FLAG_WITH_EVENT;
|
||
|
|
const payload = Buffer.isBuffer(message.payload) ? message.payload : Buffer.from(message.payload || '');
|
||
|
|
const serialization = rawPayload ? SERIALIZATION_RAW : SERIALIZATION_JSON;
|
||
|
|
|
||
|
|
let size = 4;
|
||
|
|
if (containsEvent(typeFlag)) {
|
||
|
|
size += 4;
|
||
|
|
}
|
||
|
|
if (containsEvent(typeFlag) && shouldHandleSessionId(message.event)) {
|
||
|
|
size += 4 + Buffer.byteLength(message.sessionId || '', 'utf8');
|
||
|
|
}
|
||
|
|
size += 4 + payload.length;
|
||
|
|
|
||
|
|
const buffer = Buffer.allocUnsafe(size);
|
||
|
|
buildHeader(type, typeFlag, serialization).copy(buffer, 0);
|
||
|
|
|
||
|
|
let offset = 4;
|
||
|
|
if (containsEvent(typeFlag)) {
|
||
|
|
offset = writeInt(buffer, message.event || 0, offset);
|
||
|
|
}
|
||
|
|
if (containsEvent(typeFlag) && shouldHandleSessionId(message.event)) {
|
||
|
|
offset = writeStringWithLength(buffer, message.sessionId || '', offset);
|
||
|
|
}
|
||
|
|
writePayload(buffer, payload, offset);
|
||
|
|
return buffer;
|
||
|
|
}
|
||
|
|
|
||
|
|
function readStringWithLength(buffer, offsetObj) {
|
||
|
|
const size = buffer.readInt32BE(offsetObj.offset);
|
||
|
|
offsetObj.offset += 4;
|
||
|
|
if (size <= 0) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
const value = buffer.subarray(offsetObj.offset, offsetObj.offset + size).toString('utf8');
|
||
|
|
offsetObj.offset += size;
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
|
||
|
|
function readPayload(buffer, offsetObj) {
|
||
|
|
const size = buffer.readInt32BE(offsetObj.offset);
|
||
|
|
offsetObj.offset += 4;
|
||
|
|
if (size <= 0) {
|
||
|
|
return Buffer.alloc(0);
|
||
|
|
}
|
||
|
|
const payload = buffer.subarray(offsetObj.offset, offsetObj.offset + size);
|
||
|
|
offsetObj.offset += size;
|
||
|
|
return payload;
|
||
|
|
}
|
||
|
|
|
||
|
|
function unmarshal(data) {
|
||
|
|
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
||
|
|
if (buffer.length < 4) {
|
||
|
|
throw new Error('protocol message too short');
|
||
|
|
}
|
||
|
|
|
||
|
|
const typeAndFlag = buffer[1];
|
||
|
|
const type = (typeAndFlag >> 4) & 0x0f;
|
||
|
|
const typeFlag = typeAndFlag & 0x0f;
|
||
|
|
const offsetObj = { offset: 4 };
|
||
|
|
const message = {
|
||
|
|
type,
|
||
|
|
typeName: getMessageTypeName(type),
|
||
|
|
typeFlag,
|
||
|
|
event: 0,
|
||
|
|
sessionId: '',
|
||
|
|
payload: Buffer.alloc(0),
|
||
|
|
};
|
||
|
|
|
||
|
|
if (containsEvent(typeFlag)) {
|
||
|
|
message.event = buffer.readInt32BE(offsetObj.offset);
|
||
|
|
offsetObj.offset += 4;
|
||
|
|
}
|
||
|
|
if (containsEvent(typeFlag) && shouldHandleSessionId(message.event)) {
|
||
|
|
message.sessionId = readStringWithLength(buffer, offsetObj);
|
||
|
|
}
|
||
|
|
message.payload = readPayload(buffer, offsetObj);
|
||
|
|
return message;
|
||
|
|
}
|
||
|
|
|
||
|
|
function createStartConnectionMessage() {
|
||
|
|
return marshal({
|
||
|
|
type: MsgType.FULL_CLIENT,
|
||
|
|
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
|
||
|
|
event: 1,
|
||
|
|
payload: Buffer.from('{}', 'utf8'),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function createStartSessionMessage(sessionId, payload) {
|
||
|
|
return marshal({
|
||
|
|
type: MsgType.FULL_CLIENT,
|
||
|
|
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
|
||
|
|
event: 100,
|
||
|
|
sessionId,
|
||
|
|
payload: Buffer.from(JSON.stringify(payload), 'utf8'),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function createAudioMessage(sessionId, audioBuffer) {
|
||
|
|
return marshal({
|
||
|
|
type: MsgType.AUDIO_ONLY_CLIENT,
|
||
|
|
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
|
||
|
|
event: 200,
|
||
|
|
sessionId,
|
||
|
|
payload: Buffer.isBuffer(audioBuffer) ? audioBuffer : Buffer.from(audioBuffer),
|
||
|
|
}, { rawPayload: true });
|
||
|
|
}
|
||
|
|
|
||
|
|
function createChatTTSTextMessage(sessionId, payload) {
|
||
|
|
return marshal({
|
||
|
|
type: MsgType.FULL_CLIENT,
|
||
|
|
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
|
||
|
|
event: 500,
|
||
|
|
sessionId,
|
||
|
|
payload: Buffer.from(JSON.stringify({
|
||
|
|
session_id: sessionId,
|
||
|
|
start: !!payload.start,
|
||
|
|
end: !!payload.end,
|
||
|
|
content: payload.content || '',
|
||
|
|
}), 'utf8'),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function createChatRAGTextMessage(sessionId, externalRag) {
|
||
|
|
return marshal({
|
||
|
|
type: MsgType.FULL_CLIENT,
|
||
|
|
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
|
||
|
|
event: 502,
|
||
|
|
sessionId,
|
||
|
|
payload: Buffer.from(JSON.stringify({
|
||
|
|
session_id: sessionId,
|
||
|
|
external_rag: externalRag || '[]',
|
||
|
|
}), 'utf8'),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
MsgType,
|
||
|
|
MSG_TYPE_FLAG_WITH_EVENT,
|
||
|
|
unmarshal,
|
||
|
|
createStartConnectionMessage,
|
||
|
|
createStartSessionMessage,
|
||
|
|
createAudioMessage,
|
||
|
|
createChatTTSTextMessage,
|
||
|
|
createChatRAGTextMessage,
|
||
|
|
};
|