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