Zeli AvatarDeveloper docs

JavaScript SDK / Talk streams

Talk streams

A TalkMessageStream pushes text to the avatar's voice as it is produced, token by token from an upstream model for instance, instead of waiting for a whole utterance. The avatar starts speaking before the sentence is finished.

import { TalkMessageStream } from "@zeligate/zeli-avatar";

You do not construct one. It comes from session.createTalkMessageStream(), which requires the box's control gateway.

Sending text

streamMessageChunk(content, endOfSpeech?): Promise<void>

Append a chunk, ending the utterance when endOfSpeech is true.

contentstringRequired

The next chunk of the utterance.

endOfSpeechbooleanOptionaldefault: false

Set on the final chunk.

endMessage(): Promise<void>

Signal the end of the utterance, if it has not already ended. Safe to call more than once, and a no-op on a stream that was interrupted.

send(text, options?): Promise<void>

The options-object form. It stays alongside streamMessageChunk because it carries startOfSpeech, which the shorter form has no equivalent for.

textstringRequired

The next chunk of the utterance.

startOfSpeechbooleanOptional

Whether this chunk starts the utterance. Left unset, the first chunk is the start and later ones are not, which is what you want almost always. Set it explicitly to override: false on the first chunk when you are continuing an utterance the box has already begun, true on a later chunk to restart one.

endOfSpeechbooleanOptionaldefault: false

Set on the final chunk.

Sending on a stream that has ended or been interrupted throws a SessionError, not a bare Error, so a documented catch (e) { if (e instanceof ZeliError) ... } still covers the single most likely caller mistake.

Reading state

getState(): TalkStreamState

Where this utterance has got to. Four states, not one boolean:

StateMeaning
unstartedCreated, nothing sent yet.
streamingBeing fed text.
interruptedThe avatar was cut off partway.
endedFinished normally.

Interruption wins over ending: an utterance the user talked over reports interrupted even though it is also finished.

isActive(): boolean

Whether this utterance can still take more text. True until it ends or is interrupted.

getCorrelationId(): string

The id tying this stream's chunks to the resulting transcript message. A lowercase UUID v4, dashes included. See Correlation ids.

Why four states and not a boolean

A boolean cannot answer the question a developer actually asks after a barge-in: do I retry this, or was it delivered? The old closed getter read true for both an utterance the user talked over and one that finished saying what it meant to say, which is exactly the distinction that matters. It is deprecated for that reason, not for its spelling.

Example

import { ZeliEvent } from "@zeligate/zeli-avatar";
 
const talk = session.createTalkMessageStream({ tone: "confident" });
 
for await (const token of model.stream(prompt)) {
  if (!talk.isActive()) break;          // the listener barged in
  await talk.streamMessageChunk(token);
}
await talk.endMessage();
 
if (talk.getState() === "interrupted") {
  console.log("cut off:", talk.getCorrelationId());
}

Barge-in from a UI control:

stopButton.onclick = () => session.interruptPersona(talk.getCorrelationId());
 
client.on(ZeliEvent.TALK_STREAM_INTERRUPTED, (correlationId) => {
  console.log("box stopped", correlationId);
});

await using works too, since Symbol.asyncDispose calls endMessage():

await using talk = session.createTalkMessageStream();
await talk.streamMessageChunk("Hello ");
await talk.streamMessageChunk("world.");
// endMessage() runs at scope exit

Deprecated aliases

DeprecatedUse instead
TalkStream (the exported name)TalkMessageStream
closed (getter)getState() or isActive()
close()endMessage()

TalkStream is an alias of the class rather than a subclass, so instanceof holds both ways and a value created under one name is accepted anywhere the other is expected. The aliases are removed at 1.0.

Zeli Avatar · real-time avatars over WebRTC · self-hostable · AU data residency · source