Skip to main content

oxi_agent/tools/
tts_tool.rs

1/// TTS tool — text-to-speech synthesis.
2///
3/// Converts text to spoken audio using available TTS engines.
4/// Supports local (kokoro-js/ONNX) and remote (xAI Grok Voice) backends.
5/// The omp implementation supports both local neural TTS and xAI's API.
6/// This is a practical stub that documents available options.
7use super::{AgentTool, AgentToolResult, ToolContext, ToolError, ToolExecutionMode, ToolTier};
8use async_trait::async_trait;
9use serde_json::{Value, json};
10use tokio::sync::oneshot;
11
12/// TtsTool — text-to-speech synthesis.
13pub struct TtsTool;
14
15#[async_trait]
16impl AgentTool for TtsTool {
17    fn name(&self) -> &str {
18        "tts"
19    }
20
21    fn label(&self) -> &str {
22        "TTS"
23    }
24
25    fn description(&self) -> &str {
26        concat!(
27            "Convert text to spoken audio using text-to-speech synthesis. ",
28            "Specify the text to speak and optionally the voice and format. ",
29            "Produces an audio file (MP3 or WAV) at the specified path. ",
30            "Supports multiple voices and languages."
31        )
32    }
33
34    fn essential(&self) -> bool {
35        false
36    }
37
38    fn parameters_schema(&self) -> Value {
39        json!({
40            "type": "object",
41            "properties": {
42                "text": {
43                    "type": "string",
44                    "description": "Text to convert to speech."
45                },
46                "voice": {
47                    "type": "string",
48                    "description": "Voice to use (e.g., 'alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer')."
49                },
50                "format": {
51                    "type": "string",
52                    "enum": ["mp3", "wav"],
53                    "description": "Output audio format."
54                },
55                "output_path": {
56                    "type": "string",
57                    "description": "Path to save the audio file."
58                }
59            },
60            "required": ["text"]
61        })
62    }
63
64    fn intent(&self) -> Option<&str> {
65        Some("Convert text to speech")
66    }
67
68    fn execution_mode(&self) -> ToolExecutionMode {
69        ToolExecutionMode::SequentialOnly
70    }
71
72    fn tool_tier(&self) -> ToolTier {
73        ToolTier::Read
74    }
75
76    async fn execute(
77        &self,
78        _tool_call_id: &str,
79        params: Value,
80        _signal: Option<oneshot::Receiver<()>>,
81        _ctx: &ToolContext,
82    ) -> Result<AgentToolResult, ToolError> {
83        let text = params
84            .get("text")
85            .and_then(|v| v.as_str())
86            .ok_or_else(|| "Missing required parameter: text".to_string())?;
87
88        let voice = params
89            .get("voice")
90            .and_then(|v| v.as_str())
91            .unwrap_or("alloy");
92
93        let format = params
94            .get("format")
95            .and_then(|v| v.as_str())
96            .unwrap_or("mp3");
97
98        let output_path = params
99            .get("output_path")
100            .and_then(|v| v.as_str())
101            .unwrap_or("/tmp/tts_output.mp3");
102
103        // Full implementation would use:
104        // - Local: kokoro-js (ONNX neural TTS)
105        // - Remote: xAI Grok Voice API, OpenAI TTS API
106        // For now, provide documentation.
107
108        Ok(AgentToolResult::success(format!(
109            concat!(
110                "TTS synthesis requested.\n",
111                "Text: {}\n",
112                "Voice: {}\n",
113                "Format: {}\n",
114                "Output: {}\n\n",
115                "TTS engine not yet connected. To generate speech, use shell tools:\n",
116                "  macOS: `say \"{}\" -o {}.{}`\n",
117                "  Linux: `espeak \"{}\" -w {}.wav`\n\n",
118                "Full TTS support requires a TTS backend (kokoro-js local or API key)."
119            ),
120            text, voice, format, output_path, text, output_path, format, text, output_path
121        )))
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[tokio::test]
130    async fn test_tts_basic() {
131        let tool = TtsTool;
132        let params = json!({
133            "text": "Hello, this is a test.",
134            "voice": "alloy"
135        });
136        let result = tool
137            .execute("id", params, None, &ToolContext::default())
138            .await
139            .unwrap();
140        assert!(result.success);
141        assert!(result.output.contains("TTS synthesis requested"));
142    }
143}