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