Skip to content

feat: accept an optional utterance id on talk stream chunks - #229

Open
sr-anam wants to merge 1 commit into
mainfrom
feat/talk-stream-utterance-id
Open

feat: accept an optional utterance id on talk stream chunks#229
sr-anam wants to merge 1 commit into
mainfrom
feat/talk-stream-utterance-id

Conversation

@sr-anam

@sr-anam sr-anam commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Pairs with the engine change that reads an optional utteranceId off each talk stream message (TalkStreamRequest.UtteranceId in webrtc/shared/websocket_signalling_service.go, carried through ExternalInputTextEvent into InputChatMessage.process).

Semantics come from the engine and are unchanged here:

  • Chunks sharing a key belong to one utterance.
  • A different key on a later chunk closes the current utterance and starts a new one.
  • An omitted key means "same utterance as before", which is what every existing caller sends.
  • A canonical UUIDv4 key is echoed back as MessageStreamEvent.utteranceId and in the session report, so callers can join their own ids against what the persona spoke. Any other key gets an engine-minted id instead.

Changes

TalkMessageStreamPayload gains an optional utteranceId. streamMessageChunk(content, endOfSpeech, utteranceId?) takes it as a third argument and only writes it to the payload when non-empty, so callers that ignore it send identical bytes to before.

const stream = anam.createTalkMessageStream();
await stream.streamMessageChunk('Checking the weather.', false, idA);
await stream.streamMessageChunk(' Here it is.', true, idB); // new utterance

Deliberately not included

  • No stream-level default on createTalkMessageStream/startTalkMessageStream. Per-chunk is the superset: pass the same id on every chunk for a single-utterance turn. Worth adding if callers find that repetitive.
  • No client-side validation of the id. The engine rejects keys over 100 characters with a session warning and mints its own id for anything that is not a canonical UUIDv4, so a second copy of those rules here would be one more thing to keep in sync.
  • endMessage() sends no id. It is an empty end-of-speech chunk, and empty already means "continue the current utterance".

Testing

npm test passes. Added testTalkStreamChunkCarriesUtteranceId to test/utteranceIdHarness.js: it drives three chunks through a stubbed signalling client and asserts the id is present on the first and third, absent on the second (which passes no id), alongside the existing start/end of speech flags.

Not covered: no live session against an engine build carrying the matching change. The wire field name and optionality were read from the engine worktree rather than confirmed end to end.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Cd7EUajs4Lyfzt4RgLAF7d


Summary by cubic

Adds an optional utteranceId to talk stream chunks so clients can group chunks into utterances and join their IDs to what the persona spoke. Existing callers that omit the ID keep identical wire payloads and behavior.

  • Extend TalkMessageStreamPayload with optional utteranceId; streamMessageChunk(content, endOfSpeech, utteranceId?) only sets it when non-empty.
  • Behavior: same ID = same utterance; changing the ID starts a new utterance; omitting it continues the current utterance.
  • Canonical UUIDv4 IDs are echoed on MessageStreamEvent.utteranceId and in the session report.
  • No migration needed; to adopt, pass a canonical UUIDv4 per utterance on each chunk.
  • Tests: add testTalkStreamChunkCarriesUtteranceId to verify presence/omission across chunks and start/end-of-speech flags.

Written for commit f57deae. Summary will update on new commits.

Review in cubic

The engine's talk stream now reads an optional `utteranceId` on each
chunk: chunks sharing a key form one utterance, a change starts a new
one, and an omitted key continues the current utterance. A canonical
UUIDv4 comes back on caption events and in the session report, so a
caller can join their own ids against what the persona spoke.

`streamMessageChunk` takes the id as a third optional argument and only
adds it to the wire payload when it is non-empty, so existing callers
send the same bytes as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cd7EUajs4Lyfzt4RgLAF7d
Copilot AI lite review requested due to automatic review settings August 19, 2026 19:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for an optional utteranceId on talk-stream input chunks so callers can group chunks into utterances (without changing payloads for existing callers that omit the id).

Changes:

  • Extend TalkMessageStream.streamMessageChunk(partialMessage, endOfSpeech, utteranceId?) to optionally include utteranceId in the signalling payload.
  • Add optional utteranceId?: string to TalkMessageStreamPayload.
  • Add a harness test asserting utteranceId is present only when provided on talk stream chunks.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
test/utteranceIdHarness.js Adds a harness test to verify utteranceId is conditionally included on talk stream chunk payloads.
src/types/TalkMessageStream.ts Adds an optional utteranceId argument and conditionally forwards it on chunk payloads.
src/types/signalling/TalkMessageStreamPayload.ts Extends the talk stream payload type with optional utteranceId and documents semantics.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +182 to 187
return testTalkStreamChunkCarriesUtteranceId().then(() =>
console.log('utteranceIdHarness: all tests passed'),
);
}

main();

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/utteranceIdHarness.js">

<violation number="1" location="test/utteranceIdHarness.js:182">
P2: Handle the promise returned by `main()` so assertion failures cannot be treated as unhandled rejections. Under Node's `warn` or `none` modes, the harness can otherwise finish with a successful exit status after this test rejects.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment on lines +182 to +184
return testTalkStreamChunkCarriesUtteranceId().then(() =>
console.log('utteranceIdHarness: all tests passed'),
);

@cubic-dev-ai cubic-dev-ai Bot Aug 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Handle the promise returned by main() so assertion failures cannot be treated as unhandled rejections. Under Node's warn or none modes, the harness can otherwise finish with a successful exit status after this test rejects.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/utteranceIdHarness.js, line 182:

<comment>Handle the promise returned by `main()` so assertion failures cannot be treated as unhandled rejections. Under Node's `warn` or `none` modes, the harness can otherwise finish with a successful exit status after this test rejects.</comment>

<file context>
@@ -150,14 +151,37 @@ function testHistoryShapeUnchangedWithoutUtteranceIds() {
   testPublishedMessageIsNotMutatedByLaterChunks();
   testHistoryShapeUnchangedWithoutUtteranceIds();
-  console.log('utteranceIdHarness: all tests passed');
+  return testTalkStreamChunkCarriesUtteranceId().then(() =>
+    console.log('utteranceIdHarness: all tests passed'),
+  );
</file context>
Suggested change
return testTalkStreamChunkCarriesUtteranceId().then(() =>
console.log('utteranceIdHarness: all tests passed'),
);
return testTalkStreamChunkCarriesUtteranceId()
.then(() => console.log('utteranceIdHarness: all tests passed'))
.catch((error) => {
console.error(error);
process.exitCode = 1;
});
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants