Skip to content

Add Linux STT support + harden LLM normalization output - #13

Open
F0urO4 wants to merge 2 commits into
renjfk:mainfrom
F0urO4:feat/linux-stt-support
Open

Add Linux STT support + harden LLM normalization output#13
F0urO4 wants to merge 2 commits into
renjfk:mainfrom
F0urO4:feat/linux-stt-support

Conversation

@F0urO4

@F0urO4 F0urO4 commented Aug 19, 2026

Copy link
Copy Markdown

Summary

The STT path was macOS-only. listInputDevices() used system_profiler and startRecording() spawned sox with -t coreaudio. On Linux, /stt-mic returned "No input devices found" and recording produced an empty WAV. This PR adds Linux support and fixes two LLM normalization issues I hit while testing on Pop!_OS 24.04 (PipeWire).

Changes

1. Linux STT support (feat)

  • listInputDevices(): branch on process.platform. On Linux, use pactl list short sources and filter out .monitor sinks. Works with both PulseAudio and PipeWire (PipeWire exposes PulseAudio-compatible sources).
  • startRecording(): use -t pulseaudio on Linux instead of -t coreaudio.
  • Drop the sox silence 1 0.1 1% effect on Linux: it gates recording start on input exceeding 1% and on PulseAudio often yields an empty WAV because the gate never triggers before the user speaks. Push-to-talk (ctrl+r toggle) does not need auto-start gating, so the effect is kept only on macOS where it was originally tuned.
  • README: document Linux install (apt/dnf + whisper.cpp build from source) and add a CPU performance note pointing CPU-only users at base.en instead of large-v3-turbo (large-v3-turbo transcribes a 3s clip in ~36s on CPU vs ~2s for base.en).

2. LLM normalization output hardening (fix)

Small local LLMs (e.g. llama3.2:3b via Ollama) often wrap their output in preambles like "Here is the cleaned-up transcription:" and surround the text in quotes, despite the system prompt instructing output-only. The user would then see the wrapper text appear in their OpenCode prompt instead of just the cleaned transcription.

  • cleanLLMOutput(): strips common preambles, surrounding quotes, and trailing notes from LLM output. Exported and unit-tested.
  • normalizeTranscription(): filter out auto-generated session titles ("New session - <timestamp>") before passing them to the LLM as context. Small LLMs sometimes fold these literally into the output ("This is a new session: 2026-08-19T18:58:56.307Z") instead of using them to resolve ambiguous references. Only meaningful titles are now passed, and the prompt is clearer about not including them in output.
  • Strengthen the user prompt: "Output ONLY the cleaned text, no quotes, no preamble".

Testing

  • npm run check — 0 lint errors, formatting clean (oxlint + oxfmt)
  • npm test — 12/12 tests pass (10 existing + 2 new for cleanLLMOutput)
  • Manual end-to-end test on Pop!_OS 24.04 (PipeWire) with a JLab USB microphone:
    • /stt-mic correctly lists both built-in and USB microphones
    • ctrl+r → speak → ctrl+r records, transcribes with base.en, normalizes via Ollama, and appends clean text to the OpenCode prompt
    • LLM output no longer contains preambles or quoted wrappers

Notes

  • No macOS behavior changes — all Linux code paths are behind process.platform === "darwin" checks.
  • No new dependencies.
  • The silence effect change is the only behavioral difference on macOS: none — it's still applied on Darwin exactly as before.

F0urO4 added 2 commits August 19, 2026 13:00
…recording)

The STT path was macOS-only: listInputDevices() used system_profiler and
startRecording() spawned sox with -t coreaudio. On Linux this meant
/stt-mic returned 'No input devices found' and recording produced an
empty WAV.

- listInputDevices(): branch on process.platform; on Linux use
  'pactl list short sources' and filter out .monitor sinks. Works with
  both PulseAudio and PipeWire (PipeWire exposes PulseAudio-compatible
  sources).
- startRecording(): use -t pulseaudio on Linux instead of -t coreaudio.
- Drop the sox 'silence 1 0.1 1%' effect on Linux: it gates recording
  start on input exceeding 1% and on PulseAudio often yields an empty
  WAV because the gate never triggers before the user speaks. Push-to-
  talk (ctrl+r toggle) does not need auto-start gating, so the effect
  is kept only on macOS where it was originally tuned.
- README: document Linux install (apt/dnf + whisper.cpp build from
  source) and add a CPU performance note pointing CPU-only users at
  base.en instead of large-v3-turbo.
…quotes

Small local LLMs (e.g. llama3.2:3b via Ollama) often wrap their output
in preambles like 'Here is the cleaned-up transcription:' and surround
the text in quotes, despite the system prompt instructing output-only.
The user would then see the wrapper text appear in their OpenCode prompt
instead of just the cleaned transcription.

- Add cleanLLMOutput() to strip common preambles, surrounding quotes,
  and trailing notes from LLM output. Exported and unit-tested.
- normalizeTranscription(): filter out auto-generated session titles
  ('New session - <timestamp>') before passing them to the LLM as
  context. Small LLMs sometimes fold these literally into the output
  ('This is a new session: 2026-08-19T18:58:56.307Z') instead of using
  them to resolve ambiguous references. Only meaningful titles are now
  passed, and the prompt is clearer about not including them in output.
- Strengthen the user prompt: 'Output ONLY the cleaned text, no quotes,
  no preamble'.
- Add tests for cleanLLMOutput covering preamble stripping, quote
  removal, and pass-through of already-clean text.
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