use std::time::Duration;
use serde_json::{json, Map, Value};
use crate::client::Client;
use crate::errors::{Error, Result};
use crate::Config;
#[derive(Debug, Clone, Default)]
pub struct BootstrapOptions {
pub resume_loop_id: Option<String>,
pub workspace: Option<String>,
pub user_id: Option<String>,
pub client_workspace_id: Option<String>,
pub stream_delivery: String,
pub is_ephemeral: bool,
}
impl BootstrapOptions {
pub fn new() -> Self {
Self {
stream_delivery: "adaptive".into(),
..Default::default()
}
}
}
pub async fn connect_with_retries(
client: &Client,
max_retries: u32,
retry_delay: Duration,
) -> Result<()> {
let max = if max_retries == 0 { 40 } else { max_retries };
let delay = if retry_delay.is_zero() {
Duration::from_millis(250)
} else {
retry_delay
};
let mut last = None;
for attempt in 1..=max {
match client.connect().await {
Ok(()) => return Ok(()),
Err(e) => {
last = Some(e);
if attempt < max {
tokio::time::sleep(delay).await;
}
}
}
}
Err(last.unwrap_or_else(|| Error::msg("connect failed")))
}
pub async fn bootstrap_loop_session(
client: &Client,
opts: BootstrapOptions,
cfg: Option<&Config>,
) -> Result<Map<String, Value>> {
let default_cfg = Config::default();
let cfg = cfg.unwrap_or(&default_cfg);
let delivery = if matches!(
opts.stream_delivery.as_str(),
"batch" | "adaptive" | "streaming"
) {
opts.stream_delivery.as_str()
} else {
"adaptive"
};
let loop_id = if let Some(resume) = opts
.resume_loop_id
.as_ref()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
{
resume
} else {
let mut params = Map::new();
if let Some(ws) = &opts.workspace {
if !ws.is_empty() {
params.insert("client_workspace".into(), json!(ws));
}
}
if let Some(uid) = &opts.user_id {
if !uid.is_empty() {
params.insert("user_id".into(), json!(uid));
}
}
if let Some(wsid) = &opts.client_workspace_id {
if !wsid.is_empty() {
params.insert("client_workspace_id".into(), json!(wsid));
}
}
if opts.is_ephemeral {
params.insert("is_ephemeral".into(), json!(true));
}
let resp = client
.request("loop_new", params, cfg.loop_status_timeout)
.await
.map_err(|e| Error::msg(format!("loop_new: {e}")))?;
let lid = resp
.get("loop_id")
.and_then(|v| v.as_str())
.unwrap_or("")
.trim()
.to_string();
if lid.is_empty() {
return Err(Error::msg("loop_new response missing loop_id"));
}
lid
};
client
.loop_subscribe(&loop_id, delivery)
.await
.map_err(|e| Error::msg(format!("loop_subscribe: {e}")))?;
let mut out = Map::new();
out.insert("type".into(), json!("session_ready"));
out.insert("loop_id".into(), json!(loop_id));
out.insert("success".into(), json!(true));
out.insert("autopilot_mode".into(), json!("solo"));
Ok(out)
}