onlyne_client/backend/
select.rs1use super::*;
2use onlyne_config::{Placement, validate_drive_placement};
3
4pub fn process_env() -> BTreeMap<String, String> {
5 std::env::vars().collect()
6}
7
8fn env_nonempty(env: &BTreeMap<String, String>, key: &str) -> bool {
9 env.get(key).is_some_and(|value| !value.is_empty())
10}
11
12fn orca_host_present(env: &BTreeMap<String, String>) -> bool {
13 env_nonempty(env, "ORCA_PANE_KEY")
14 || env_nonempty(env, "ORCA_TERMINAL_HANDLE")
15 || env_nonempty(env, "ORCA_WORKTREE_ID")
16}
17
18fn zellij_host_present(env: &BTreeMap<String, String>) -> bool {
19 env.contains_key("ZELLIJ")
20}
21
22fn probed_placement(env: &BTreeMap<String, String>) -> Option<Placement> {
25 if orca_host_present(env) {
26 Some(Placement::Orca)
27 } else if zellij_host_present(env) {
28 Some(Placement::Zellij)
29 } else {
30 None
31 }
32}
33
34pub fn detect_placement(
47 env: &BTreeMap<String, String>,
48 declared: Option<SessionPlacement>,
49) -> Result<PlacementDetection> {
50 let explicit = env
51 .get("ONLYNE_BACKEND")
52 .map(|value| value.trim().to_string())
53 .filter(|value| !value.is_empty() && !value.eq_ignore_ascii_case("auto"));
54 if let Some(name) = &explicit {
55 let Some(placement) = SessionPlacement::parse(name) else {
56 return Err(UnknownPlacement(name.clone()).into());
57 };
58 return Ok(PlacementDetection {
59 placement,
60 source: SelectionSource::Explicit,
61 explicit: Some(name.clone()),
62 });
63 }
64 if let Some(placement) = declared {
65 return Ok(PlacementDetection {
66 placement,
67 source: SelectionSource::Declared,
68 explicit: None,
69 });
70 }
71 let probed = probed_placement(env);
72 Ok(PlacementDetection {
73 placement: SessionPlacement::Named(probed.unwrap_or(Placement::Headless)),
74 source: if probed.is_some() {
75 SelectionSource::Probe
76 } else {
77 SelectionSource::Fallback
78 },
79 explicit: None,
80 })
81}
82
83pub fn doctor_report(env: &BTreeMap<String, String>) -> Value {
87 let detected = detect_placement(env, None);
88 let found = detected.as_ref().ok();
89 let placement = found.map(|detected| detected.placement.as_str());
90 let selection = found.map(|detected| match detected.source {
91 SelectionSource::Explicit => "explicit",
92 SelectionSource::Declared => "declared",
93 SelectionSource::Probe => "probe",
94 SelectionSource::Fallback => "fallback",
95 });
96 let binary = match placement {
97 Some("orca") => Some(
98 env.get("ORCA_CLI_COMMAND")
99 .filter(|value| !value.is_empty())
100 .cloned()
101 .unwrap_or_else(|| "orca".into()),
102 ),
103 Some("zellij") => Some(
104 env.get("ZELLIJ_COMMAND")
105 .filter(|value| !value.is_empty())
106 .cloned()
107 .unwrap_or_else(|| "zellij".into()),
108 ),
109 _ => None,
112 };
113 let mut report = serde_json::json!({
114 "placement": placement,
115 "placement_selection": selection,
116 "binary": binary,
117 "explicit": found.and_then(|detected| detected.explicit.clone()),
118 });
119 if let Err(error) = &detected {
120 report["refusal"] = Value::String(error.to_string());
121 }
122 report
123}
124
125pub fn backend_for(
145 drive: onlyne_config::Drive,
146 placement: SessionPlacement,
147 runner: Arc<dyn Runner>,
148 policy: WorktreePolicy,
149 acp: &AcpOptions,
150) -> Result<Box<dyn SessionBackend>> {
151 if let Some(named) = placement.named() {
152 validate_drive_placement(drive, named).map_err(|message| anyhow::anyhow!("{message}"))?;
153 }
154 Ok(match placement {
155 SessionPlacement::Fake => Box::new(fake::FakeBackend::new()),
156 SessionPlacement::Named(Placement::Orca) => {
157 Box::new(orca::OrcaBackend::with_policy(runner, policy))
158 }
159 SessionPlacement::Named(Placement::Zellij) => Box::new(zellij::ZellijBackend::new(runner)),
160 SessionPlacement::Named(Placement::Headless) => match drive {
161 onlyne_config::Drive::Acp => Box::new(acp::AcpBackend::new(acp.clone())),
162 onlyne_config::Drive::Plugin | onlyne_config::Drive::Exec => {
163 Box::new(exec::ExecBackend::new())
164 }
165 },
166 SessionPlacement::Named(Placement::External) => match drive {
167 onlyne_config::Drive::Plugin => Box::new(external::ExternalBackend::new()),
168 onlyne_config::Drive::Acp | onlyne_config::Drive::Exec => {
169 Box::new(exec::ExecBackend::new())
170 }
171 },
172 })
173}