Skip to main content

codetether_agent/tool/
voice_stream.rs

1//! Voice Stream Tool - Stream and play TTS audio via browser.
2//!
3//! Provides `speak_stream` (text → TTS → open in browser) and `play`
4//! (play a previously generated job_id in the browser).
5
6mod actions;
7mod schema;
8
9use super::{Tool, ToolResult};
10use anyhow::{Context, Result};
11use async_trait::async_trait;
12use serde::Deserialize;
13use serde_json::Value;
14
15pub struct VoiceStreamTool {
16    client: reqwest::Client,
17}
18
19impl Default for VoiceStreamTool {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl VoiceStreamTool {
26    pub fn new() -> Self {
27        Self {
28            client: reqwest::Client::builder()
29                .timeout(std::time::Duration::from_secs(120))
30                .user_agent("CodeTether-Agent/1.0")
31                .build()
32                .expect("Failed to build HTTP client"),
33        }
34    }
35}
36
37/// Resolve the Voice API base URL from env.
38pub(crate) fn voice_api_url() -> String {
39    std::env::var("CODETETHER_VOICE_API_URL")
40        .unwrap_or_else(|_| "https://voice.quantum-forge.io".to_string())
41}
42
43/// Input parameters deserialized from the LLM call.
44#[derive(Deserialize)]
45pub struct Params {
46    pub action: String,
47    #[serde(default)]
48    pub text: Option<String>,
49    #[serde(default)]
50    pub voice_id: Option<String>,
51    #[serde(default)]
52    pub language: Option<String>,
53    #[serde(default)]
54    pub job_id: Option<String>,
55}
56
57#[async_trait]
58impl Tool for VoiceStreamTool {
59    fn id(&self) -> &str {
60        "voice_stream"
61    }
62    fn name(&self) -> &str {
63        "VoiceStream"
64    }
65    fn description(&self) -> &str {
66        "Stream/play TTS audio in the browser. Actions: speak_stream, play."
67    }
68    fn parameters(&self) -> Value {
69        schema::json_schema()
70    }
71
72    async fn execute(&self, params: Value) -> Result<ToolResult> {
73        let p: Params = serde_json::from_value(params).context("Invalid voice_stream params")?;
74        actions::dispatch(&self.client, &p).await
75    }
76}