Repository navigation
fix!: preserve voice model selection and prevent PipeWire self-ducking - #25
Conversation
|
Hi @malhashemi90! When you have a moment, could you take a look at this PR? CI seems to be awaiting maintainer approval, and I'd love your feedback. Thanks! |
malhashemi
left a comment
There was a problem hiding this comment.
Hi @francocarballar, thank you so much for this! It's a genuinely great PR: you found and reproduced two real bugs (the new-session model mismatch and PipeWire self-ducking), the write-up is excellent, and the tests around the startup races are really thorough. I appreciate the care you put into it.
I've been through it in detail. The model fix, the startup cancellation and the Linux ducking fix look good to me. The playback changes touch the audio engine on every platform and have only been live-tested on Linux, so I'd like to land them separately. Would you be up for splitting this into two PRs?
1. This PR: model selection, startup cancellation and Linux ducking, with a few small changes:
-
No model selected: rather than refusing to start the call ("Choose a model before starting a voice call."), could
callModelreturnundefinedand the call just go ahead without setting a model (skip themodelonsession.createand skipswitchModel)? It's an edge case and I'd rather keep the old behaviour there than add a new failure. -
Cancel, then retry:
stop()invalidates the generation, butpreparing(index.ts) andstarting(controller.ts) staytrueuntil the cancelled await finishes. So:- stopping during model sync and immediately starting again skips the new attempt;
/voice-newwhile a Home session is being created cancels the first attempt and silently drops the second, so no call starts.
Could the gates be released (or owned per generation) when an attempt is cancelled?
2. A new PR for the playback changes (engine.rs / io.rs / resample.rs), with two things I'd love to see addressed:
- Unbounded backlog:
service_playbacknow decodes up to 32 packets per cycle even when the speaker ring is full, and the remainder accumulates in the resampler'sVecDeque. Previously the 4 s ring bounded it. If the output callback stops (device unplugged or switched), memory and latency grow for the rest of the call. Could pending audio be capped (dropping the oldest), or the call ended on output failure? - Clear acknowledgement with no way out: after a clear, the engine waits for the renderer to acknowledge before it refills. If the callback has stopped, GPT-Live stays silent for good while the mic is still live. A timeout or fallback would cover it.
I'll test that PR on macOS (built-in mic and speakers, interruptions, talking over it, switching the output device) once it's up, since I can't reproduce the PipeWire setup here.
Thanks again, this is really appreciated! 🙏
Use the same model and variant for session creation and persistence. Do not start on a session the user left during preparation. Co-authored-by: OpenAI <noreply@openai.com>
Guard asynchronous startup and helper callbacks after stop or disposal. Stop late RPC replies by their returned call ID, and cancel Home preparation before navigation or model persistence. Co-authored-by: OpenAI <noreply@openai.com>
Libera las trabas por intento al cancelar para que un retry inmediato o /voice-new durante la creación de sesión pueda arrancar. Co-Authored-By: OpenAI <noreply@openai.com>
9c16438 to
826d50a
Compare
|
Hi @malhashemi, I’ve split the changes as requested. PR #25 now covers model selection, startup cancellation, and Linux ducking. PR #29 contains the playback changes and is ready for the macOS test you mentioned. Could you review them when you have a chance? Thanks! |
|
Thanks so much @francocarballar! The split and the follow-up fixes are exactly what I was hoping for: the default-model fallback and the per-attempt startup gates both look great, and the tests are excellent. Merging now. Really appreciate the effort you put into this. 🙏 |
Purpose
The voice plugin connects OpenCode’s terminal interface to a live audio call.
OpenCode’s terminal interface provides the active session and the model selected for written prompts.
Startup flow
The plugin first reuses the active session or creates one.
The plugin saves the selected model to the session before starting audio.
The controller starts the audio helper and requests the live call.
If no model is selected, the plugin leaves the new session’s model unset.
OpenCode applies its normal default to that session.
Cancellation
The controller releases ownership of a cancelled startup immediately.
The controller ignores results from older attempts.
This lets you retry or run
/voice-newwhile an earlier session request is still pending.Linux ducking
PipeWire can omit the process ID (PID) from the helper’s audio stream.
Without that ID, the ducking logic could lower the call’s own output.
The plugin excludes the helper’s known stream name when the PID is missing.
The ducking logic still uses the PID when available and continues to cover other applications.
Limits and verification
These changes prevent stale startup attempts and self-ducking, but they do not guarantee provider availability or hardware behavior.
bun run checkpassed with 83 tests.Linux native tests passed with three tests.
Clippy and formatting checks passed.
Real-call testing on macOS has not been performed.