{"record":{"id":"64b50651c93342ce","repo":"Hmbown/CodeWhale","slug":"speech-model-cannot-be-empty","errorCode":null,"errorMessage":"Speech model cannot be empty","messagePattern":"Speech model cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/client.rs","lineNumber":2345,"sourceCode":"    ///\n    /// The spoken text is placed in an `assistant` message because Xiaomi\n    /// MiMo's TTS chat-completions surface expects that shape. The optional\n    /// `instruction` is a `user` message that controls style, voice design, or\n    /// voice-clone performance and is not spoken verbatim.\n    pub async fn synthesize_speech(\n        &self,\n        request: SpeechSynthesisRequest,\n    ) -> Result<SpeechSynthesisResponse> {\n        if self.api_provider != crate::config::ApiProvider::XiaomiMimo {\n            anyhow::bail!(\n                \"speech synthesis requires provider 'xiaomi-mimo' (current: {})\",\n                self.api_provider.as_str()\n            );\n        }\n\n        let model = request.model.trim().to_string();\n        if model.is_empty() {\n            anyhow::bail!(\"Speech model cannot be empty\");\n        }\n        let text = request.text.trim().to_string();\n        if text.is_empty() {\n            anyhow::bail!(\"Speech text cannot be empty\");\n        }\n\n        let audio_format = normalize_audio_format(&request.audio_format);\n        let model = wire_model_for_provider_route(self.api_provider, &self.base_url, &model);\n        let model_lower = model.to_ascii_lowercase();\n        let instruction = request\n            .instruction\n            .as_deref()\n            .map(str::trim)\n            .filter(|value| !value.is_empty());\n        let voice = request\n            .voice\n            .as_deref()\n            .map(str::trim)","sourceCodeStart":2327,"sourceCodeEnd":2363,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/client.rs#L2327-L2363","documentation":"Input validation in `synthesize_speech`: after the provider gate, the requested model string is trimmed and must be non-empty before the request can be built. This fires before text validation and before any network I/O — purely a caller-input check on `SpeechSynthesisRequest.model`.","triggerScenarios":"Invoking the speech command with no `--model` argument (or a whitespace-only value) when no configured default supplies one; programmatically building a `SpeechSynthesisRequest` with `model: \"\"` or `\"   \"`.","commonSituations":"Command-line invocation that assumes a default TTS model exists; UI state where the model selector was left blank; scripts passing an empty variable (`--model \"$TTS_MODEL\"` with the env var unset).","solutions":["Pass an explicit TTS model id, e.g. `--model xiaomi-mimo-tts` (or the specific MiMo voice model you intend).","If embedding, default the model in the caller before constructing the request: `let model = request.model.trim(); if model.is_empty() { /* pick default */ }`.","Check for unset shell variables when scripting (`--model \"${TTS_MODEL:?TTS_MODEL not set}\"`)."],"exampleFix":"# before\n/speech --model \"\" \"hello\"\n# after\n/speech --model xiaomi-mimo-tts \"hello\"","handlingStrategy":"validation","validationCode":"fn speech_request_valid(model: &str, text: &str) -> bool {\n    !model.trim().is_empty() && !text.trim().is_empty()\n}","typeGuard":"fn non_blank(s: &str) -> Option<&str> {\n    let t = s.trim();\n    (!t.is_empty()).then_some(t)\n}","tryCatchPattern":null,"preventionTips":["Default the TTS model in the command layer when the argument is omitted.","Validate CLI args (model + text non-blank) before dispatching to the client.","Use shell parameter expansion guards like `--model \"${TTS_MODEL:?unset}\"` in scripts."],"tags":["speech","tts","validation","input-validation"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}