feat: accept an optional utterance id on talk stream chunks - #229
Conversation
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
There was a problem hiding this comment.
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 includeutteranceIdin the signalling payload. - Add optional
utteranceId?: stringtoTalkMessageStreamPayload. - Add a harness test asserting
utteranceIdis 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.
| return testTalkStreamChunkCarriesUtteranceId().then(() => | ||
| console.log('utteranceIdHarness: all tests passed'), | ||
| ); | ||
| } | ||
|
|
||
| main(); |
There was a problem hiding this comment.
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
| return testTalkStreamChunkCarriesUtteranceId().then(() => | ||
| console.log('utteranceIdHarness: all tests passed'), | ||
| ); |
There was a problem hiding this comment.
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>
| 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; | |
| }); |
Summary
Pairs with the engine change that reads an optional
utteranceIdoff each talk stream message (TalkStreamRequest.UtteranceIdinwebrtc/shared/websocket_signalling_service.go, carried throughExternalInputTextEventintoInputChatMessage.process).Semantics come from the engine and are unchanged here:
MessageStreamEvent.utteranceIdand 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
TalkMessageStreamPayloadgains an optionalutteranceId.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.Deliberately not included
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.endMessage()sends no id. It is an empty end-of-speech chunk, and empty already means "continue the current utterance".Testing
npm testpasses. AddedtestTalkStreamChunkCarriesUtteranceIdtotest/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
utteranceIdto 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.TalkMessageStreamPayloadwith optionalutteranceId;streamMessageChunk(content, endOfSpeech, utteranceId?)only sets it when non-empty.MessageStreamEvent.utteranceIdand in the session report.testTalkStreamChunkCarriesUtteranceIdto verify presence/omission across chunks and start/end-of-speech flags.Written for commit f57deae. Summary will update on new commits.