From 8e97f926ce13c5a17444ebe0909e87495251bfdd Mon Sep 17 00:00:00 2001 From: draxxris <24984408+draxxris@users.noreply.github.com> Date: Tue, 30 Jun 2026 07:38:22 -0400 Subject: [PATCH] change: support custom tmpDir path --- README.md | 1 + lib/stt.js | 38 +++++++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 3830f3b..be2a2fa 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,7 @@ For unauthenticated local endpoints (e.g. Ollama): - `reasoningEffort` _(optional)_ - reasoning level for models that support it - `chatTemplateKwargs` _(optional)_ - extra keyword arguments passed to the model's chat template (e.g. `{"enable_thinking": false}` for Qwen models to disable chain-of-thought) - `retries` _(optional)_ - number of retry attempts for transient LLM failures +- `tmpDir` _(optional)_ - directory used for the temporary STT recording file (default `/tmp`) ### Logging diff --git a/lib/stt.js b/lib/stt.js index 51eb539..809703b 100644 --- a/lib/stt.js +++ b/lib/stt.js @@ -10,7 +10,8 @@ let sttApiEndpoint = null; let sttApiModel = null; let sttApiKeyEnv = null; -const WAV_FILE = "/tmp/opencode-stt.wav"; +const WAV_FILENAME = "opencode-stt.wav"; +let tmpDir = "/tmp"; const MODELS_DIRS = [ path.join(os.homedir(), ".local", "share", "whisper-cpp"), @@ -120,9 +121,10 @@ function startRecording(kv, toast, logger) { return; } + const wavFile = path.join(tmpDir, WAV_FILENAME); forceKillSox(logger); try { - fs.unlinkSync(WAV_FILE); + fs.unlinkSync(wavFile); } catch {} soxStderr = ""; @@ -132,7 +134,7 @@ function startRecording(kv, toast, logger) { soxProc = spawn( "sox", - [...inputArgs, "-r", "16000", "-c", "1", "-b", "16", WAV_FILE, "silence", "1", "0.1", "1%"], + [...inputArgs, "-r", "16000", "-c", "1", "-b", "16", wavFile, "silence", "1", "0.1", "1%"], { stdio: ["ignore", "ignore", "pipe"], detached: false, @@ -195,6 +197,7 @@ function getModelPath(kv) { } function transcribe(kv, logger) { + const wavFile = path.join(tmpDir, WAV_FILENAME); const mp = getModelPath(kv); logger?.log("STT", `Local transcription requested model=${mp}`, "debug"); if (!fs.existsSync(mp)) { @@ -203,19 +206,19 @@ function transcribe(kv, logger) { error: `Model not found: ${getModelName(kv)}. Download from huggingface.co/ggerganov/whisper.cpp`, }); } - if (!fs.existsSync(WAV_FILE)) { - logger?.log("STT", `Recording file missing: ${WAV_FILE}`, "error"); + if (!fs.existsSync(wavFile)) { + logger?.log("STT", `Recording file missing: ${wavFile}`, "error"); return Promise.resolve({ error: "No recording file - sox may have failed to capture audio" }); } - if (fs.statSync(WAV_FILE).size <= 44) { - logger?.log("STT", `Recording file empty: ${WAV_FILE}`, "warn"); + if (fs.statSync(wavFile).size <= 44) { + logger?.log("STT", `Recording file empty: ${wavFile}`, "warn"); return Promise.resolve({ error: "Recording is empty - no audio captured" }); } return new Promise((resolve) => { let stdout = ""; let stderr = ""; - const proc = spawn("whisper-cli", ["-m", mp, "-f", WAV_FILE, "-np", "-nt"], { + const proc = spawn("whisper-cli", ["-m", mp, "-f", wavFile, "-np", "-nt"], { stdio: ["ignore", "pipe", "pipe"], }); logger?.log("STT", `Started whisper-cli pid=${proc.pid}`, "debug"); @@ -330,20 +333,21 @@ async function transcribeApi(kv, logger) { logger?.log("STT", "STT API transcription skipped: API not configured", "warn"); return { error: "STT API not configured" }; } + const wavFile = path.join(tmpDir, WAV_FILENAME); const model = kv.get("stt.api.model") || sttApiModel; logger?.log("STT", `STT API transcription requested model=${model}`, "debug"); - if (!fs.existsSync(WAV_FILE)) { - logger?.log("STT", `Recording file missing: ${WAV_FILE}`, "error"); + if (!fs.existsSync(wavFile)) { + logger?.log("STT", `Recording file missing: ${wavFile}`, "error"); return { error: "No recording file - sox may have failed to capture audio" }; } - if (fs.statSync(WAV_FILE).size <= 44) { - logger?.log("STT", `Recording file empty: ${WAV_FILE}`, "warn"); + if (fs.statSync(wavFile).size <= 44) { + logger?.log("STT", `Recording file empty: ${wavFile}`, "warn"); return { error: "Recording is empty - no audio captured" }; } try { - const audioBuffer = await fs.promises.readFile(WAV_FILE); + const audioBuffer = await fs.promises.readFile(wavFile); const apiKey = sttApiKeyEnv ? process.env[sttApiKeyEnv] : null; const useOpenRouterFormat = isOpenRouterEndpoint(sttApiEndpoint); @@ -487,6 +491,14 @@ export function registerSTT(api, kv, complete, prompts, opts, logger) { ); } + tmpDir = opts?.tmpDir || "/tmp"; + try { + fs.mkdirSync(tmpDir, { recursive: true }); + } catch (err) { + logger?.log("STT", `Failed to create tmpDir ${tmpDir}: ${err.message}`, "warn"); + } + logger?.log("STT", `STT temp dir=${tmpDir}`, "debug"); + return [ { title: sttApiEndpoint ? "STT: record/transcribe (API)" : "STT: record/transcribe",