ITADN

ReceivedMessage missing isFinal — no way to know when a segment is done streaming

#945Openacrdlph 创建于 2026-03-12
Enhancement
A
acrdlphcommented
**Describe the bug** `ReceivedMessage` does not expose whether a segment has been finalized. The `lk.transcription_final` attribute is read internally by `TranscriptionStreamReceiver` (to clean up `partialMessages`), but it is stripped before the message reaches consumers via `Session.messages`. This makes it impossible for UI code to distinguish a segment that is still streaming from one that is complete. Common use cases that require this signal: - Showing action buttons (play, translate) only after a segment finishes - Triggering auto-translation of completed segments - Gating per-message interactions to avoid acting on partial text **Observed behavior** For **user transcripts**, `lk.transcription_final` transitions from `"false"` to `"true"` in the stream attributes — so the attribute-based path works. For **agent transcripts**, the attribute is `"false"` on every chunk. Finality is implicit: the text stream closes when the segment is done. But this stream-close event is not surfaced to consumers — the `for try await message in reader` loop simply ends, and no final `ReceivedMessage` is emitted. Additionally, the **last** agent segment's stream often does not close promptly. In testing, it stays open until external activity occurs (e.g. the user starts speaking or the next agent turn begins), leaving the final segment stuck in a "streaming" state indefinitely. **Server** - LiveKit Cloud, version 1.9.12 - Region: Germany 2 **Client** - SDK: iOS (`client-sdk-swift`) - Version: 2.12.1 **To Reproduce** 1. Connect to a room with a voice agent (livekit-agents Python, agents >= 1.0) 2. Agent speaks a multi-segment response (3+ bubbles) 3. Observe `Session.messages` — all `ReceivedMessage` values have no `isFinal` property 4. There is no way to determine when each segment's text is complete vs. still being appended **Expected behavior** `ReceivedMessage` should expose an `isFinal: Bool` property so consumers can react to segment completion. Two signals should set it to `true`: 1. **Attribute-based**: When `lk.transcription_final` is `"true"` in the stream attributes (already works for user transcripts) 2. **Stream-close**: When the text stream ends, the segment is implicitly finalized — a final `ReceivedMessage` with `isFinal: true` should be emitted **Proposed fix** We've been running a local patch that works well. The change is minimal (3 files, additive only, non-breaking): 1. Add `public let isFinal: Bool` to `ReceivedMessage` (default `false`, backward-compatible Codable) 2. Pass through `isFinal` from attributes in `processIncoming` 3. After the stream's `for try await` loop ends, yield one final `ReceivedMessage(isFinal: true)` if not already marked **Remaining issue even with the patch** The stream-close signal works reliably for **middle segments** (the stream closes when the next segment's stream opens). However, the **last segment's stream** often stays open well beyond when the agent has finished speaking — sometimes not closing until the user speaks next. A client-side debounce (act after ~1.5s of no text changes) works as a fallback, but ideally the server/agent would close the stream promptly when the segment is complete. **Additional context** - `TranscriptionSegment` (used in the deprecated delegate path) already has `isFinal: Bool` — this is just about surfacing the equivalent on the stream-based `ReceivedMessage` - The default value of `false` makes this fully backward-compatible - Happy to open a PR if this direction looks right
3 条评论