car_parslee/
mobile_runtime.rs1use serde::Serialize;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
11pub struct MobileRuntimeRegistration {
12 pub name: String,
13 pub source: String,
14 pub url: String,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub token: Option<String>,
17 pub capabilities: Vec<String>,
18}
19
20impl MobileRuntimeRegistration {
21 pub fn new(url: impl Into<String>, token: Option<String>) -> Self {
22 let url = url.into();
23 Self {
24 name: "Parslee Core".to_string(),
25 source: "car".to_string(),
26 url,
27 token,
28 capabilities: vec![
29 "chat".to_string(),
30 "approval".to_string(),
31 "a2ui".to_string(),
32 "notify_linked_device".to_string(),
33 "verified_goals".to_string(),
34 ],
35 }
36 }
37}
38
39pub async fn register(
40 api_base: &str,
41 bearer: &str,
42 registration: &MobileRuntimeRegistration,
43) -> Result<(), String> {
44 let client = reqwest::Client::builder()
45 .timeout(std::time::Duration::from_secs(4))
46 .connect_timeout(std::time::Duration::from_secs(2))
47 .redirect(reqwest::redirect::Policy::none())
48 .build()
49 .map_err(|e| format!("build Parslee mobile runtime client: {e}"))?;
50 let response = client
51 .post(endpoint(api_base))
52 .bearer_auth(bearer)
53 .json(registration)
54 .send()
55 .await
56 .map_err(|e| format!("register Parslee mobile runtime: {e}"))?;
57 let status = response.status();
58 if !status.is_success() {
59 let body = response.text().await.unwrap_or_default();
60 return Err(format!(
61 "register Parslee mobile runtime failed: HTTP {status}: {body}"
62 ));
63 }
64 Ok(())
65}
66
67pub fn endpoint(api_base: &str) -> String {
68 format!("{}/mobile/runtimes", api_base.trim_end_matches('/'))
69}
70
71pub fn configured_api_base() -> String {
72 car_auth::api_base(None)
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn registration_body_matches_mobile_discovery_shape() {
81 let body = MobileRuntimeRegistration::new(
82 "wss://home.example/mobile?device=mac studio",
83 Some("pair+/=123".to_string()),
84 );
85
86 assert_eq!(body.name, "Parslee Core");
87 assert_eq!(body.source, "car");
88 assert_eq!(body.url, "wss://home.example/mobile?device=mac studio");
89 assert_eq!(body.token.as_deref(), Some("pair+/=123"));
90 assert!(body.capabilities.contains(&"verified_goals".to_string()));
91 }
92
93 #[test]
94 fn endpoint_uses_mobile_runtimes_contract() {
95 assert_eq!(
96 endpoint("https://api.parslee.ai/"),
97 "https://api.parslee.ai/mobile/runtimes"
98 );
99 }
100}