use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use crate::acp::AgentIoOptions;
use crate::model_id::{ModelBackend, ParsedModel};
use super::sdk_session::SdkSession;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BridgeKind {
Cursor,
Pi,
Codex,
}
pub struct SdkClient {
pub model: ParsedModel,
pub kind: BridgeKind,
pub io: AgentIoOptions,
pub prompts_log_run_dir: Option<PathBuf>,
pub max_acp_retries: u32,
pub(crate) session: Option<SdkSession>,
pub(crate) session_cwd: Option<PathBuf>,
pub(crate) last_agent_id: Option<String>,
pub(crate) timing: Option<Arc<Mutex<crate::run_timing::RunTiming>>>,
}
struct SdkClientInit {
model: ParsedModel,
io: AgentIoOptions,
max_acp_retries: u32,
}
impl SdkClient {
#[must_use]
pub fn new_cursor(model: ParsedModel, io: AgentIoOptions) -> Self {
Self::from_init(SdkClientInit {
model,
io,
max_acp_retries: crate::support_paths::DEFAULT_MAX_ACP_RETRIES,
})
}
#[must_use]
pub fn new_pi(model: ParsedModel, io: AgentIoOptions) -> Self {
Self::from_init(SdkClientInit {
model,
io,
max_acp_retries: crate::support_paths::DEFAULT_MAX_ACP_RETRIES,
})
}
#[must_use]
pub fn new_codex(model: ParsedModel, io: AgentIoOptions) -> Self {
Self::from_init(SdkClientInit {
model,
io,
max_acp_retries: crate::support_paths::DEFAULT_MAX_ACP_RETRIES,
})
}
#[must_use]
pub fn with_max_retries(
model: ParsedModel,
io: AgentIoOptions,
max_acp_retries: u32,
) -> Self {
Self::from_init(SdkClientInit {
model,
io,
max_acp_retries,
})
}
#[must_use]
fn from_init(init: SdkClientInit) -> Self {
let kind = bridge_kind_from_backend(init.model.backend);
Self {
model: init.model,
kind,
io: init.io,
prompts_log_run_dir: None,
max_acp_retries: if init.max_acp_retries == 0 {
1
} else {
init.max_acp_retries
},
session: None,
session_cwd: None,
last_agent_id: None,
timing: None,
}
}
pub fn set_run_timing(&mut self, timing: Option<Arc<Mutex<crate::run_timing::RunTiming>>>) {
self.timing = timing.clone();
sync_timing_to_open_session(self);
}
#[must_use]
pub fn attach_run_timing_for_session(&mut self) -> Arc<Mutex<crate::run_timing::RunTiming>> {
let model = self.model.canonical();
let timing = crate::run_timing::attach_new_run_timing(&mut self.timing, &model);
sync_timing_to_open_session(self);
timing
}
#[must_use]
pub const fn has_open_coder_session(&self) -> bool {
self.session.is_some()
}
#[must_use]
pub const fn keeps_coder_session_for_process_life(&self) -> bool {
true
}
#[must_use]
pub fn last_coder_prompt_agent_response(&self) -> Option<String> {
let session = self.session.as_ref()?;
let text = session
.last_response
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
if text.is_empty() || text == "\0" {
None
} else {
Some(text)
}
}
}
fn sync_timing_to_open_session(client: &mut SdkClient) {
if let Some(session) = client.session.as_mut() {
session.timing = client.timing.clone();
}
}
const fn bridge_kind_from_backend(backend: ModelBackend) -> BridgeKind {
match backend {
ModelBackend::Cursor => BridgeKind::Cursor,
ModelBackend::Pi => BridgeKind::Pi,
ModelBackend::Codex => BridgeKind::Codex,
}
}