1use anyhow::Result;
2use rsipstack::dialog::{authenticate::Credential, invitation::InviteOption};
3use serde::{Deserialize, Serialize};
4use serde_with::skip_serializing_none;
5use std::collections::HashMap;
6
7use crate::{
8 media::{
9 ambiance::AmbianceOption, recorder::RecorderOption, track::media_pass::MediaPassOption,
10 vad::VADOption,
11 },
12 synthesis::SynthesisOption,
13 transcription::TranscriptionOption,
14};
15
16pub mod app;
17pub mod call;
18pub mod callrecord;
19pub mod config;
20pub mod event;
21pub mod handler;
22pub mod locator;
23pub mod media;
24pub mod net_tool;
25pub mod playbook;
26pub mod synthesis;
27pub mod transcription;
28pub mod useragent;
29
30#[derive(Debug, Deserialize, Serialize, Default, Clone)]
31#[serde(default)]
32pub struct SipOption {
33 pub username: Option<String>,
34 pub password: Option<String>,
35 pub realm: Option<String>,
36 pub contact: Option<String>,
37 pub headers: Option<HashMap<String, String>>,
38}
39
40#[skip_serializing_none]
41#[derive(Debug, Deserialize, Serialize, Clone)]
42#[serde(rename_all = "camelCase")]
43pub struct CallOption {
44 pub denoise: Option<bool>,
45 pub offer: Option<String>,
46 pub callee: Option<String>,
47 pub caller: Option<String>,
48 pub recorder: Option<RecorderOption>,
49 pub vad: Option<VADOption>,
50 pub asr: Option<TranscriptionOption>,
51 pub tts: Option<SynthesisOption>,
52 pub media_pass: Option<MediaPassOption>,
53 pub handshake_timeout: Option<u64>,
55 pub enable_ipv6: Option<bool>,
56 pub inactivity_timeout: Option<u64>, pub sip: Option<SipOption>,
58 pub extra: Option<HashMap<String, String>>,
59 pub codec: Option<String>, pub ambiance: Option<AmbianceOption>,
61 pub eou: Option<EouOption>,
62 pub realtime: Option<RealtimeOption>,
63}
64
65impl Default for CallOption {
66 fn default() -> Self {
67 Self {
68 denoise: None,
69 offer: None,
70 callee: None,
71 caller: None,
72 recorder: None,
73 asr: None,
74 vad: None,
75 tts: None,
76 media_pass: None,
77 handshake_timeout: None,
78 inactivity_timeout: Some(50), enable_ipv6: None,
80 sip: None,
81 extra: None,
82 codec: None,
83 ambiance: None,
84 eou: None,
85 realtime: None,
86 }
87 }
88}
89
90impl CallOption {
91 pub fn check_default(&mut self) {
92 if let Some(tts) = &mut self.tts {
93 tts.check_default();
94 }
95 if let Some(asr) = &mut self.asr {
96 asr.check_default();
97 }
98 if let Some(realtime) = &mut self.realtime {
99 realtime.check_default();
100 }
101 }
102
103 pub fn build_invite_option(&self) -> Result<InviteOption> {
104 let mut invite_option = InviteOption::default();
105 if let Some(offer) = &self.offer {
106 invite_option.offer = Some(offer.clone().into());
107 }
108 if let Some(callee) = &self.callee {
109 invite_option.callee = callee.clone().try_into()?;
110 }
111 let caller_uri = if let Some(caller) = &self.caller {
112 if caller.starts_with("sip:") || caller.starts_with("sips:") {
114 caller.clone()
115 } else {
116 format!("sip:{}", caller)
117 }
118 } else if let Some(username) = self.sip.as_ref().and_then(|sip| sip.username.as_ref()) {
119 let domain = self
122 .sip
123 .as_ref()
124 .and_then(|sip| sip.realm.as_ref())
125 .map(|s| s.as_str())
126 .unwrap_or("127.0.0.1");
127 format!("sip:{}@{}", username, domain)
128 } else {
129 "sip:active-call@127.0.0.1".to_string()
131 };
132 invite_option.caller = caller_uri.try_into()?;
133
134 if let Some(sip) = &self.sip {
135 invite_option.credential = Some(Credential {
136 username: sip.username.clone().unwrap_or_default(),
137 password: sip.password.clone().unwrap_or_default(),
138 realm: sip.realm.clone(),
139 });
140 invite_option.headers = sip.headers.as_ref().map(|h| {
141 h.iter()
142 .map(|(k, v)| rsip::Header::Other(k.clone(), v.clone()))
143 .collect::<Vec<_>>()
144 });
145 sip.contact.as_ref().map(|c| match c.clone().try_into() {
146 Ok(u) => {
147 invite_option.contact = u;
148 }
149 Err(_) => {}
150 });
151 }
152 Ok(invite_option)
153 }
154}
155
156#[skip_serializing_none]
157#[derive(Debug, Deserialize, Serialize, Clone)]
158#[serde(rename_all = "camelCase")]
159pub struct ReferOption {
160 pub denoise: Option<bool>,
161 pub timeout: Option<u32>,
162 pub moh: Option<String>,
163 pub asr: Option<TranscriptionOption>,
164 pub auto_hangup: Option<bool>,
166 pub sip: Option<SipOption>,
167}
168
169#[skip_serializing_none]
170#[derive(Clone, Debug, Deserialize, Serialize, Default)]
171#[serde(rename_all = "camelCase")]
172pub struct EouOption {
173 pub r#type: Option<String>,
174 pub endpoint: Option<String>,
175 #[serde(alias = "apiKey")]
176 pub secret_key: Option<String>,
177 pub secret_id: Option<String>,
178 pub timeout: Option<u32>,
180 pub extra: Option<HashMap<String, String>>,
181}
182
183#[derive(Debug, Clone, Serialize, Hash, Eq, PartialEq)]
184pub enum RealtimeType {
185 #[serde(rename = "openai")]
186 OpenAI,
187 #[serde(rename = "azure")]
188 Azure,
189 Other(String),
190}
191
192impl<'de> Deserialize<'de> for RealtimeType {
193 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194 where
195 D: serde::Deserializer<'de>,
196 {
197 let value = String::deserialize(deserializer)?;
198 match value.as_str() {
199 "openai" => Ok(RealtimeType::OpenAI),
200 "azure" => Ok(RealtimeType::Azure),
201 _ => Ok(RealtimeType::Other(value)),
202 }
203 }
204}
205
206#[skip_serializing_none]
207#[derive(Clone, Debug, Deserialize, Serialize, Default)]
208#[serde(rename_all = "camelCase")]
209pub struct RealtimeOption {
210 pub provider: Option<RealtimeType>,
211 pub model: Option<String>,
212 #[serde(alias = "apiKey")]
213 pub secret_key: Option<String>,
214 pub secret_id: Option<String>,
215 pub endpoint: Option<String>,
216 pub turn_detection: Option<serde_json::Value>,
217 pub tools: Option<Vec<serde_json::Value>>,
218 pub extra: Option<HashMap<String, String>>,
219}
220
221impl RealtimeOption {
222 pub fn check_default(&mut self) {
223 if self.secret_key.is_none() {
224 self.secret_key = std::env::var("OPENAI_API_KEY").ok();
225 }
226 }
227}
228
229pub type Spawner = fn(
230 std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>,
231) -> tokio::task::JoinHandle<()>;
232static EXTERNAL_SPAWNER: std::sync::OnceLock<Spawner> = std::sync::OnceLock::new();
233
234pub fn set_spawner(spawner: Spawner) -> Result<(), Spawner> {
235 EXTERNAL_SPAWNER.set(spawner)
236}
237
238pub fn spawn<F>(future: F) -> tokio::task::JoinHandle<()>
239where
240 F: std::future::Future<Output = ()> + Send + 'static,
241{
242 if let Some(spawner) = EXTERNAL_SPAWNER.get() {
243 spawner(Box::pin(future))
244 } else {
245 tokio::spawn(future)
246 }
247}