Skip to content

fix(voice): keep realtime turn after chat ctx timeout#2083

Open
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
subduing-spearing-cheery
Open

fix(voice): keep realtime turn after chat ctx timeout#2083
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
subduing-spearing-cheery

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Ports livekit/agents#6445 to JS: realtime user turns are no longer dropped when the pre-reply chat context push times out with a provider-reported realtime error.

Changes:

  • Add/export RealtimeError for realtime provider timeout/failure classification.
  • Treat RealtimeError from AgentActivity.realtimeReplyTask chat-context push as best-effort and still call generateReply.
  • Preserve unexpected chat-context push failures by marking the SpeechHandle done with the error.
  • Update OpenAI realtime chat-context ack timeout to throw RealtimeError("update_chat_ctx timed out.").
  • Add adapted regression coverage for success, realtime timeout, and unexpected-error paths.
Source diff coverage
Source file Classification Target coverage
livekit-agents/livekit/agents/voice/agent_activity.py Ported agents/src/voice/agent_activity.ts: catches provider RealtimeError from the pre-reply updateChatCtx call, logs a warning, records the local user message, and still generates the realtime reply. Unexpected errors log, mark the SpeechHandle failed, and return, matching the source failure path.
tests/test_realtime_reply_chat_ctx.py Adapted agents/src/voice/agent_activity.test.ts: ported the three source cases into Vitest using target ChatContext, ToolContext, SpeechHandle, and a minimal fake realtime activity because the target has no tests/fake_realtime.py counterpart or standalone test_realtime_reply_chat_ctx file.

Additional target infrastructure ported because the source behavior depends on it:

Source behavior/infrastructure Target coverage
llm.RealtimeError classification Added RealtimeError in agents/src/llm/realtime.ts and exported it from agents/src/llm/index.ts.
SpeechHandle._mark_done(error=...) plus SpeechHandle.exception() observability Added non-throwing error storage to agents/src/voice/speech_handle.ts via _markDone(error?) and exception().
OpenAI realtime update_chat_ctx timed out. provider error Updated plugins/openai/src/realtime/realtime_model.ts to throw llm.RealtimeError("update_chat_ctx timed out.") for chat-context ack timeout.

No source diff files were dropped as not applicable.

Verification

Passed:

  • pnpm test -- agents/src/voice/agent_activity.test.ts agents/src/voice/speech_handle.test.ts
  • pnpm --filter @livekit/agents --filter @livekit/agents-plugin-openai... build
  • pnpm test -- agents/src/voice/agent_activity.test.ts agents/src/voice/speech_handle.test.ts plugins/openai/src/realtime/realtime_model.test.ts
  • pnpm --filter @livekit/agents --filter @livekit/agents-plugin-openai lint exited successfully with existing warnings.
  • cue-cli realtime smoke validation passed: launched a temporary OpenAI realtime JS worker, drove a text turn, and asserted function_tools_executed -> conversation_item_added(.item.message.role="ASSISTANT").

Attempted full touched-package tests:

  • pnpm test -- agents/src plugins/openai/src failed on unrelated existing/environment issues: agents/src/voice/amd.test.ts expectation/timeouts, and OpenAI plugin test import failures before package builds.
  • After building, pnpm test -- plugins/openai/src still failed on unrelated fixture/environment issues: corrupted or placeholder plugins/silero/dist/silero_vad.onnx for stt.test.ts, and an existing gpt-realtime-whisper VAD requirement error in tts.test.ts. Other OpenAI plugin tests passed.

Additional attempted check:

  • pnpm --filter @livekit/agents --filter @livekit/agents-plugin-openai api:check is blocked by the repo's existing API Extractor limitation on export * as ___ in agents/dist/index.d.ts.

Ported from livekit/agents#6445

Original PR description

Incident. Calling session.generate_reply(user_input=...) on a realtime session pushes the message via update_chat_ctx before requesting the reply. That await was unguarded: when the OpenAI plugin's 5s ack timeout fired (RealtimeError("update_chat_ctx timed out.")), the error propagated as an unhandled exception in the turn task — the turn died, the user got silence, and nothing was reported on the SpeechHandle.

Fix. The push is best-effort — the plugin sends all conversation.item.create events before waiting for acks, so a timeout means missing acknowledgements, not missing items. So:

RealtimeError → log a warning and still call generate_reply (the server usually has the context).
Unexpected exceptions → mark the SpeechHandle done with the error, observable via SpeechHandle.exception() #6304 , without crashing the turn task.
Deliberately out of scope: no retry of the push itself, and no change to the plugin's timeout; the two other update_chat_ctx call sites (interrupted-sync and tool-output paths) already have their own RealtimeError handling and are untouched.

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from a team as a code owner July 22, 2026 03:57
@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 083d5a4

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

This PR includes changesets to release 38 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai 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

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from longcw July 22, 2026 03:57

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment on lines +74 to +80
export class RealtimeError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = 'RealtimeError';
Error.captureStackTrace(this, RealtimeError);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 New public error type ships without documentation

A new publicly exported class is added (RealtimeError at agents/src/llm/realtime.ts:74) without the TypeDoc comment that the repository requires for every new class/interface/method.
Impact: Public API documentation is incomplete for consumers of the framework.

CONTRIBUTING.md documentation requirement

CONTRIBUTING.md states: "If writing new methods/interfaces/enums/classes, document them. This project uses TypeDoc... and every new addition has to be properly documented." The newly exported RealtimeError class (agents/src/llm/realtime.ts:74-80, re-exported in agents/src/llm/index.ts:76) has no doc comment.

Suggested change
export class RealtimeError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = 'RealtimeError';
Error.captureStackTrace(this, RealtimeError);
}
}
/**
* Error raised by a realtime provider when an operation fails or times out
* (e.g. a chat-context update or reply generation).
*/
export class RealtimeError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = 'RealtimeError';
Error.captureStackTrace(this, RealtimeError);
}
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +187 to +193
exception(): unknown {
if (!this.doneFut.done) {
throw new Error('SpeechHandle is not done yet');
}

return this._error;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 New public method added without documentation

A new public method is added (exception() at agents/src/voice/speech_handle.ts:187) without the TypeDoc comment that the repository requires for every new method.
Impact: Public API documentation is incomplete and the throwing behavior of the method is undocumented for callers.

CONTRIBUTING.md documentation requirement

CONTRIBUTING.md states: "If writing new methods/interfaces/enums/classes, document them." The new public exception() method on SpeechHandle throws when the handle is not yet done, which is a behavior worth documenting.

Suggested change
exception(): unknown {
if (!this.doneFut.done) {
throw new Error('SpeechHandle is not done yet');
}
return this._error;
}
/**
* Returns the error that caused this SpeechHandle to complete, if any.
*
* @throws Error if the SpeechHandle is not done yet.
*/
exception(): unknown {
if (!this.doneFut.done) {
throw new Error('SpeechHandle is not done yet');
}
return this._error;
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

0 participants