Skip to content

fix(openai): forward session.update on RealtimeSession.updateOptions#1303

Merged
toubatbrian merged 4 commits intomainfrom
claude/jolly-lovelace-XAl1E
Apr 30, 2026
Merged

fix(openai): forward session.update on RealtimeSession.updateOptions#1303
toubatbrian merged 4 commits intomainfrom
claude/jolly-lovelace-XAl1E

Conversation

@toubatbrian
Copy link
Copy Markdown
Contributor

Summary

Ports livekit/agents#5531 ("fix(openai): forward session.update on RealtimeModel.update_options") from the Python repo.

Upstream bug

RealtimeSession.updateOptions() compared the incoming options against RealtimeModel._options and then mutated that same object before forwarding the update. Because both sides of the diff read from the same mutable object, the diff always saw "no change" and no session.update event was ever sent to OpenAI — so the realtime session never actually applied the new options.

The shared-state problem is additionally harmful with multiple sessions attached to the same RealtimeModel: one session's updateOptions would silently overwrite another session's options.

Fix

Give each RealtimeSession its own shallow copy of RealtimeOptions, populated from the model's options at construction time. All reads/writes inside RealtimeSession now go through this._options instead of this.oaiRealtimeModel._options, so:

  • updateOptions compares against — and mutates — per-session state, so its diff is meaningful and the session.update event is emitted.
  • Sessions no longer stomp on each other when they share a model.

Ported changes

  • plugins/openai/src/realtime/realtime_model.ts
    • New private field RealtimeSession._options: RealtimeOptions.
    • Constructor initializes it via a shallow spread: this._options = { ...realtimeModel._options }.
    • Mechanically rewrote every this.oaiRealtimeModel._options reference inside RealtimeSession to this._options (connection headers, URL building, Azure branching, session-update construction, tool-update event, updateOptions, max-session-duration timeout, modalities resolution, etc.).
    • All references inside RealtimeModel itself are untouched — only RealtimeSession now reads from a local copy.
  • .changeset/openai-realtime-session-opts-copy.md (patch bump for @livekit/agents-plugin-openai).

Per-line // Ref: python … comments are added at the two structural change sites (the new field and the constructor copy), matching the repo convention in CLAUDE.md for Python ports.

Implementation nuances / notes on parity

  • Shallow copy choice. Python uses dataclasses.replace(realtime_model._opts), which produces a new dataclass instance with the same field values (shallow). In TS, { ...realtimeModel._options } is the direct equivalent — we copy the top-level field references. Nested objects (connOptions, turnDetection, inputAudioTranscription, etc.) are still shared by reference, which matches Python's dataclasses.replace semantics exactly. This is deliberate: updateOptions replaces whole option fields rather than mutating nested ones, so shared nested references don't reintroduce the bug.
  • Scope of updateOptions differs from Python. The JS updateOptions currently only accepts toolChoice (see the existing TODO(brian): add other options here), whereas the Python version diffs many more options (voice, turn_detection, input_audio_transcription, input_audio_noise_reduction, speed, tracing, truncation, max_response_output_tokens). We ported the root-cause structural fix (per-session _options copy), so when additional options are added to JS's updateOptions in the future, they will automatically inherit the correct diff semantics. Porting the additional option branches themselves is left out of this PR to keep the change minimal and 1:1 with the upstream fix's intent.
  • RealtimeModel._options unchanged. The model-level options still reflect constructor inputs and are what new sessions copy from. This matches Python: RealtimeModel._opts is the template, each RealtimeSession gets an independent snapshot.
  • No behavior change at the model level. Callers who only use one session per model get identical observable behavior plus the session.update fix. Callers who reuse a model across sessions get the additional correctness improvement of isolated per-session state.

Test plan

  • pnpm build:agents — passes.
  • pnpm --filter=@livekit/agents-plugin-openai build — passes (types + tsup).
  • pnpm test -- plugins/openai — all 12 realtime_model.test.ts tests pass. (Unrelated ws/llm.test.ts failure requires OPENAI_API_KEY and predates this change.)
  • pnpm --filter=@livekit/agents-plugin-openai lint — no new errors/warnings introduced by this change (remaining warnings are pre-existing no-explicit-any / tsdoc items).
  • Manual verification in Agent Playground: call session.updateOptions({ toolChoice: ... }) mid-session and confirm a session.update frame is emitted.

cc @toubatbrian @livekit/agent-devs

🤖 This PR was generated by an automated Claude Code routine that ports merged livekit/agents plugin / core runtime PRs into livekit/agents-js. Experimental — please review the ported logic and the nuances noted above carefully.


Generated by Claude Code

Give each RealtimeSession its own shallow copy of RealtimeOptions so
updateOptions can diff against the session's own state instead of the
shared model-level state. This mirrors livekit/agents#5531.

Ports: livekit/agents#5531
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 23, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ toubatbrian
❌ claude
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Copy Markdown
Contributor Author

Requesting review from @toubatbrian and @livekit/agent-devs.

Note: GitHub doesn't let the PR author be added as a formal "Reviewer" since @toubatbrian is the author of this automated PR — tagging here instead.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 27, 2026

🦋 Changeset detected

Latest commit: d3e8847

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 26 packages
Name Type
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-xai Patch
@livekit/agents Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugins-test Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

claude added 2 commits April 27, 2026 07:44
The disable comment used @typescript-eslint/no-require-imports (the
newer rule name), but this repo's eslint config still uses the older
no-var-requires rule, so the directive didn't suppress the error.
Copy link
Copy Markdown
Member

@tinalenguyen tinalenguyen left a comment

Choose a reason for hiding this comment

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

lgtm!

@toubatbrian toubatbrian merged commit 60006aa into main Apr 30, 2026
8 of 9 checks passed
@toubatbrian toubatbrian deleted the claude/jolly-lovelace-XAl1E branch April 30, 2026 03:05
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.

4 participants