@vaibhavgupta0786/opencode-voice 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env python3
2
+ """Minimal OpenAI-compatible speech-to-text server on Apple Silicon GPU.
3
+
4
+ Same API as the faster-whisper reference server, so opencode-voice needs no
5
+ changes: POST /v1/audio/transcriptions (multipart file + model) -> {"text"}.
6
+
7
+ Differences: mlx-whisper on the Metal GPU (~2x faster than CPU
8
+ faster-whisper/small on this class of machine) and zero temp files — the
9
+ uploaded WAV is decoded in memory with the stdlib wave module.
10
+
11
+ Config (env):
12
+ MLX_MODEL HF repo (default: mlx-community/whisper-small-mlx)
13
+ VOICE_STT_PORT listen port (default: 8080)
14
+ """
15
+
16
+ import io
17
+ import json
18
+ import os
19
+ import wave
20
+ from email.parser import BytesParser
21
+ from email.policy import default as email_policy
22
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
23
+
24
+ import numpy as np
25
+ import mlx_whisper
26
+
27
+ MODEL = os.environ.get("MLX_MODEL", "mlx-community/whisper-small-mlx")
28
+ PORT = int(os.environ.get("VOICE_STT_PORT", "8080"))
29
+
30
+ print(f"loading {MODEL} ...", flush=True)
31
+ # Warm up by loading weights once; per-request calls reuse them.
32
+ _MODEL_REF = MODEL
33
+ print("ready", flush=True)
34
+
35
+
36
+ def wav_bytes_to_float32(raw: bytes) -> np.ndarray:
37
+ with wave.open(io.BytesIO(raw), "rb") as w:
38
+ if w.getframerate() != 16_000 or w.getnchannels() != 1 or w.getsampwidth() != 2:
39
+ raise ValueError(
40
+ f"expected 16kHz mono s16, got {w.getframerate()}Hz "
41
+ f"{w.getnchannels()}ch {w.getsampwidth() * 8}bit"
42
+ )
43
+ frames = w.readframes(w.getnframes())
44
+ return np.frombuffer(frames, dtype=np.int16).astype(np.float32) / 32768.0
45
+
46
+
47
+ class Handler(BaseHTTPRequestHandler):
48
+ def _json(self, code: int, payload: dict) -> None:
49
+ body = json.dumps(payload).encode()
50
+ self.send_response(code)
51
+ self.send_header("Content-Type", "application/json")
52
+ self.send_header("Content-Length", str(len(body)))
53
+ self.end_headers()
54
+ self.wfile.write(body)
55
+
56
+ def do_GET(self): # noqa: N802
57
+ if self.path == "/health":
58
+ self._json(200, {"status": "ok"})
59
+ else:
60
+ self._json(404, {"error": "not found"})
61
+
62
+ def do_POST(self): # noqa: N802
63
+ if not self.path.rstrip("/").endswith("/audio/transcriptions"):
64
+ self._json(404, {"error": "not found"})
65
+ return
66
+ length = int(self.headers.get("Content-Length", 0))
67
+ raw = self.rfile.read(length)
68
+ ctype = self.headers.get("Content-Type", "")
69
+ try:
70
+ msg = BytesParser(policy=email_policy).parsebytes(
71
+ f"Content-Type: {ctype}\r\nMIME-Version: 1.0\r\n\r\n".encode() + raw
72
+ )
73
+ audio: bytes | None = None
74
+ for part in msg.iter_parts():
75
+ if part.get_filename():
76
+ audio = part.get_content()
77
+ break
78
+ if audio is None:
79
+ self._json(400, {"error": "no file part"})
80
+ return
81
+ samples = wav_bytes_to_float32(bytes(audio))
82
+ result = mlx_whisper.transcribe(samples, path_or_hf_repo=_MODEL_REF)
83
+ self._json(200, {"text": (result.get("text") or "").strip()})
84
+ except Exception as error: # keep the endpoint fail-closed with a reason
85
+ self._json(500, {"error": f"{type(error).__name__}: {error}"})
86
+
87
+ def log_message(self, *args): # quieter logs
88
+ pass
89
+
90
+
91
+ if __name__ == "__main__":
92
+ server = ThreadingHTTPServer(("127.0.0.1", PORT), Handler)
93
+ print(f"listening on 127.0.0.1:{PORT} model={MODEL}", flush=True)
94
+ server.serve_forever()
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: Voice Check
3
+ description: Check and restart the local whisper STT server that the opencode-voice dictation plugin uses on port 8080. Use when voice dictation fails, an "STT failed" error appears, or the user asks to fix, prepare, check, or bring back voice input.
4
+ slash: true
5
+ ---
6
+
7
+ # Voice STT server check
8
+
9
+ The opencode-voice plugin transcribes dictation through a local whisper
10
+ server at http://127.0.0.1:8080. When that server is down, every take
11
+ fails with "STT failed". Work these steps in order; stop at the first one
12
+ that resolves the situation.
13
+
14
+ ## 1. Health check
15
+
16
+ curl -s -m 3 http://127.0.0.1:8080/health
17
+
18
+ If it prints `{"status": "ok"}`, the server is up — report that dictation
19
+ should work (press Fn+F9) and stop. Nothing is broken.
20
+
21
+ ## 2. Locate the server install
22
+
23
+ Check these directories in order; the server dir is the first that
24
+ contains a server file and a Python virtualenv:
25
+
26
+ 1. `~/.local/share/opencode/voice-stt` — canonical location (see the
27
+ opencode-voice repo's `docs/STT.md`)
28
+ 2. `~/.local/share/opencode/opencode-dictate` — legacy location
29
+
30
+ Inside the server dir, expect one of:
31
+
32
+ - `mlx_server.py` + `mlx-env/` — mlx-whisper on the Apple Silicon GPU
33
+ (preferred), or
34
+ - `server.py` + `stt-env/` — faster-whisper on CPU
35
+
36
+ If neither directory exists, the STT server was never installed. Point the
37
+ user to the opencode-voice repo's `docs/STT.md` and stop.
38
+
39
+ ## 3. Restart the server
40
+
41
+ Clear anything still holding the port (ignore errors):
42
+
43
+ lsof -ti:8080 | xargs kill -9
44
+
45
+ Start detached. If `mlx_server.py` exists:
46
+
47
+ cd <server-dir> && nohup ./mlx-env/bin/python3 mlx_server.py > mlx-server.log 2>&1 &
48
+
49
+ otherwise:
50
+
51
+ cd <server-dir> && WHISPER_MODEL=small nohup ./stt-env/bin/python3 server.py > stt-server.log 2>&1 &
52
+
53
+ ## 4. Wait for boot, then re-verify
54
+
55
+ Poll the health endpoint every 5 seconds, up to 30 seconds total — the
56
+ model loads from cache in roughly 10-25 s on a cold start:
57
+
58
+ curl -s -m 3 http://127.0.0.1:8080/health
59
+
60
+ ## 5. If it still will not start
61
+
62
+ Show the last 10-20 lines of the server log (`mlx-server.log` or
63
+ `stt-server.log`) and report the error you see. Do not guess further.
64
+
65
+ ## 6. Report
66
+
67
+ One line: server was up / has been restarted / failed (with the error).
68
+ Remind the user to press Fn+F9 to dictate.