Skip to content

Voice mode: surface session and request errors - #236

Merged
stippi merged 4 commits into
mainfrom
fix/voice-polish
Oct 9, 2026
Merged

stippi merged 4 commits into
mainfrom
fix/voice-polish

Conversation

@stippi

@stippi stippi commented Oct 9, 2026

Copy link
Copy Markdown
Owner

Polish for voice mode (#234), mostly around errors that were swallowed.

  • ChatGPT subscription providers are not offered for voice. A provider with codex_auth has no access to the Realtime API; the settings no longer list it, and a voice.json naming one fails with that reason instead of "has no api_key".
  • A rejected session fails with the server's reason. OpenAI accepts the WebSocket even with an invalid API key, then sends error and closes. connect() counted the handshake as success, so the agent reconnected forever and the status stayed on Connecting…. A connection now counts once session.created arrives.
  • The session settings are confirmed. connect() sends session.update and waits for session.updated; a refused update (unknown voice or transcription model, a tool schema) fails the attempt instead of leaving the session on server defaults without tools or instructions.
  • Errors are told apart by the client event they answer. response.create, response.cancel and conversation.item.truncate carry an event_id prefix. Errors for our cancel or truncate are logged only; one for our response.create frees the floor (before, any error did, even while a response was still coming). Every other error, and a response.done with status failed, shows as an Error line in the transcript and turns the status chip red (message in the tooltip) until an answer completes again.
  • Create watchdog. The floor is given back when response.create gets no response.created within 10 s.
  • Interrupted answers are marked in the transcript that seeds a renewed session, since their transcript says more than the user heard.

docs/voice-mode.md is updated.

Tests

  • voice::tests: rejected session, close before the session started, rejected reconnects end voice mode instead of looping, rejected settings, a survived error shows until an answer completes (and a cancel error does not show), failed answer reason, interrupted mark in the renewal seed.
  • voice::floor: create watchdog fires / is disarmed by response.created.
  • llm::realtime: error causes from event ids, status_details reasons.
  • voice::config: a codex_auth provider serves no realtime.

cargo fmt --check, cargo clippy -p llm -p code_assistant_core -p ui_gpui --all-targets -D warnings, cargo check --workspace --all-targets clean.

stippi added 4 commits October 9, 2026 08:36
…vider

Its token grants no access to the Realtime API, so voice mode could only
fail with a missing api_key. The settings no longer list such a provider,
and a voice.json that still names one fails with the reason.
OpenAI accepts the WebSocket even with an invalid API key, then sends an
error event and closes. connect() counted the handshake as success, so
the agent reconnected forever and the status stayed on Connecting. A
connection now counts once session.created arrives; an error event or a
close before that fails the attempt with the server's message.
response.create, response.cancel and conversation.item.truncate carry an
event_id with a per-kind prefix, so ErrorInfo::cause tells which of them
an error answers. ResponseInfo keeps status_details and names the reason
of a failed or incomplete response.
…survives

- connect() sends session.update itself and waits for session.updated. A
  refused update (unknown voice or transcription model, a tool schema)
  fails the attempt instead of leaving the session on server defaults,
  without tools or instructions.
- Errors are told apart by the client event they answer: those for our
  cancel or truncate are logged only, one for our response.create frees
  the floor (before, any error did, even while a response was coming).
  Every other error and a failed response show as an Error line in the
  transcript and on the status chip until an answer completes again.
- A watchdog frees the floor when response.create gets no
  response.created within 10 s.
- An answer the user barged into is marked as interrupted in the
  transcript that seeds a renewed session.
@stippi
stippi merged commit 871cd4e into main Oct 9, 2026
5 checks passed
@stippi
stippi deleted the fix/voice-polish branch October 9, 2026 08:24
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.

1 participant