diff --git a/src/types/TalkMessageStream.ts b/src/types/TalkMessageStream.ts index 1150dd4..7810b1a 100644 --- a/src/types/TalkMessageStream.ts +++ b/src/types/TalkMessageStream.ts @@ -73,6 +73,7 @@ export class TalkMessageStream { public async streamMessageChunk( partialMessage: string, endOfSpeech: boolean, + utteranceId?: string, ): Promise { if ( this.state !== TalkMessageStreamState.STREAMING && @@ -86,6 +87,7 @@ export class TalkMessageStream { startOfSpeech: this.state === TalkMessageStreamState.UNSTARTED, endOfSpeech: endOfSpeech, correlationId: this.correlationId, + ...(utteranceId ? { utteranceId } : {}), }; this.state = endOfSpeech ? TalkMessageStreamState.ENDED diff --git a/src/types/signalling/TalkMessageStreamPayload.ts b/src/types/signalling/TalkMessageStreamPayload.ts index 8d2136f..bbae5c8 100644 --- a/src/types/signalling/TalkMessageStreamPayload.ts +++ b/src/types/signalling/TalkMessageStreamPayload.ts @@ -3,4 +3,8 @@ export interface TalkMessageStreamPayload { startOfSpeech: boolean; endOfSpeech: boolean; correlationId: string; + // Optional client-scoped utterance key. Chunks sharing a key form one utterance; + // changing it starts a new one. Omitted means "same utterance as before". + // A canonical UUIDv4 is echoed back as MessageStreamEvent.utteranceId. + utteranceId?: string; } diff --git a/test/utteranceIdHarness.js b/test/utteranceIdHarness.js index d74f7e1..376eb54 100644 --- a/test/utteranceIdHarness.js +++ b/test/utteranceIdHarness.js @@ -9,6 +9,7 @@ const { InternalEventEmitter, } = require('../dist/main/modules/InternalEventEmitter'); const { AnamEvent, InternalEvent } = require('../dist/main/types'); +const { TalkMessageStream } = require('../dist/main/types/TalkMessageStream'); function setup() { const publicEmitter = new PublicEventEmitter(); @@ -150,6 +151,27 @@ function testHistoryShapeUnchangedWithoutUtteranceIds() { assert.ok(!('utterances' in history[0])); } +function testTalkStreamChunkCarriesUtteranceId() { + const sent = []; + const stream = new TalkMessageStream( + 'corr-1', + new InternalEventEmitter(), + { sendTalkMessage: async (payload) => sent.push(payload) }, + ); + + return Promise.resolve() + .then(() => stream.streamMessageChunk('Hello', false, 'uuid-a')) + .then(() => stream.streamMessageChunk(' again', false)) + .then(() => stream.streamMessageChunk(' bye', true, 'uuid-b')) + .then(() => { + assert.equal(sent[0].utteranceId, 'uuid-a'); + assert.ok(!('utteranceId' in sent[1])); + assert.equal(sent[2].utteranceId, 'uuid-b'); + assert.equal(sent[0].startOfSpeech, true); + assert.equal(sent[2].endOfSpeech, true); + }); +} + function main() { testUtteranceIdExposedOnStreamEvents(); testMissingOrEmptyUtteranceIdOmitted(); @@ -157,7 +179,9 @@ function main() { testHistoryPreservesNonSeparatorWhitespace(); testPublishedMessageIsNotMutatedByLaterChunks(); testHistoryShapeUnchangedWithoutUtteranceIds(); - console.log('utteranceIdHarness: all tests passed'); + return testTalkStreamChunkCarriesUtteranceId().then(() => + console.log('utteranceIdHarness: all tests passed'), + ); } main();