Skip to content

feat(realtime): bundle initial_state into livekit_join#155

Merged
AdirAmsalem merged 5 commits into
mainfrom
feat/bundle-initial-state-in-join
Jun 15, 2026
Merged

feat(realtime): bundle initial_state into livekit_join#155
AdirAmsalem merged 5 commits into
mainfrom
feat/bundle-initial-state-in-join

Conversation

@VerioN1

@VerioN1 VerioN1 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

What

Send the initial reference image/prompt inside the livekit_join message as initial_state, instead of as a separate set_image/prompt request fired after livekit_room_info arrives.

Wire change:

// before: two messages
{ "type": "livekit_join" }
{ "type": "set_image", "image_data": "" }   // sent after room info

// after: one message
{ "type": "livekit_join", "initial_state": { "type": "set_image", "image_data": "" } }

The inference server now gets the reference image/prompt with the join — no extra client round-trip waiting on livekit_room_info.

How

  • LiveKitJoinMessage gains an optional initial_state field.
  • buildInitialStateRequest() produces a single reusable { message, matchAck } from the caller's initialState.
  • request() gains a write? flag so the ack can be armed without re-sending the payload. The initial-state ack is still awaited after room info, so the readiness gate behaves exactly as before — only the message's position on the wire moved.
  • Removed the now-unused sendInitialState/sendInitialStateTracked two-step path.

Scope

Isolated to the wire format: signaling-channel.ts, the LiveKitJoinMessage type, and the affected unit tests. No error-verbosity, playground, or media-channel changes from the source branch were carried over.

Test

vitest run unit — 210 passed. tsc --noEmit and biome check clean.

🤖 Generated with Claude Code


Note

Medium Risk
Changes the realtime signaling wire contract and handshake timing; server must understand bundled initial_state, though client-side connect gating behavior is intended to stay the same.

Overview
Realtime join now sends reference image/prompt inside livekit_join as initial_state (or null as a capability marker), instead of a follow-up set_image / prompt after livekit_room_info.

buildInitialStateRequest() maps caller InitialState to the nested wire message; request() gains optional write: false so the client can wait for the ack without resending. Early set_image_ack / prompt_ack messages are buffered until after room info arms the waiter, preserving queue-timeout behavior and the existing initialStateAck readiness gate.

Reviewed by Cursor Bugbot for commit 521d5cb. Bugbot is set up for automated code reviews on this repo. Configure here.

Send the initial reference image/prompt inside the livekit_join message
(initial_state) instead of as a separate set_image/prompt request after
livekit_room_info arrives. The ack is still awaited (armed without
re-writing) so the readiness gate is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread packages/sdk/src/realtime/signaling-channel.ts
write: false,
});
this.config.observability?.endPhase("initial-state-handshake", { success: true });
if (!ack.success) throw new Error(ack.error ?? `Failed: ${request.label}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Observability marks failed ack success

Low Severity

In flushInitialState, endPhase("initial-state-handshake", { success: true }) runs before checking ack.success. When the server returns a failed set_image_ack or prompt_ack, telemetry still records a successful initial-state handshake even though the method then throws.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fc7cc3e. Configure here.

@pkg-pr-new

pkg-pr-new Bot commented Jun 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@decartai/sdk@155

commit: 521d5cb

VerioN1 and others added 2 commits June 10, 2026 16:57
The bundled livekit_join flow arms the set_image_ack/prompt_ack listener only
after livekit_room_info (flushInitialState), and handleMessage drops any ack with
no matching listener. So a bouncer that emits the bundled ack before room_info
would have it silently dropped -> handshake timeout.

Buffer set_image_ack/prompt_ack received while not yet connected (before
room_info) and let request() claim a buffered ack when its listener arms. The ack
timeout still starts only after room_info, so the queue-wait protection is
unchanged. Buffer is cleared on teardown.

Refs API-1167, API-827

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
initial_state presence is the bouncer's signal that the client is the
capability-bearing SDK that buffers an early set_image_ack/prompt_ack, so the
bouncer can ack a bundled set_image/prompt without waiting for livekit_room_info.
Always send the field (null when there is no initial state) so the marker is
unambiguous.

Refs API-1167, API-827

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f49f547. Configure here.

if (!this.connected && (msg.type === "set_image_ack" || msg.type === "prompt_ack")) {
this.bufferedAcks.push(msg);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale buffered acks poison requests

Medium Severity

While connected is still false, every set_image_ack and prompt_ack is appended to bufferedAcks, but leftover entries are not cleared after the initial-state handshake. Later request() calls (including setImage) resolve from the first buffered match and can skip writeMessage, so a duplicate or unrelated early ack can make a later image send appear successful without hitting the wire.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f49f547. Configure here.

… after testing)

Adds a bundleInitialState option (default true) threaded
connect -> StreamSession -> openAndJoin. When false, the join omits initial_state
and the set_image/prompt (including the publisher bootstrap null set_image) is
sent as its own message after room_info — the old two-step flow. A "Legacy
connection protocol" checkbox in the demo index.html drives it, to verify the new
bouncer stays backwards-compatible with old SDK versions. Revert after testing.

Refs API-1167

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VerioN1 VerioN1 force-pushed the feat/bundle-initial-state-in-join branch from 491f7ad to 14f9600 Compare June 10, 2026 15:43
…— revert after testing)"

This reverts commit 14f9600.
@AdirAmsalem AdirAmsalem merged commit addac79 into main Jun 15, 2026
5 checks passed
@AdirAmsalem AdirAmsalem deleted the feat/bundle-initial-state-in-join branch June 15, 2026 08:16
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