use std::path::Path;
use crate::acp::{AgentError, AuthError};
use crate::bridge_sdk::SDK_BRIDGE_MAX_AGE;
use crate::model_id::ModelBackend;
use super::sdk_client::SdkClient;
#[path = "sdk_client_session_spawn.rs"]
mod spawn;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CoderSessionEnsure {
Fresh,
Reused,
}
impl CoderSessionEnsure {
#[must_use]
pub const fn is_fresh(self) -> bool {
matches!(self, Self::Fresh)
}
}
impl SdkClient {
pub fn ensure_authenticated(&self) -> Result<(), AuthError> {
match self.model.backend {
ModelBackend::Cursor => crate::cursor_sdk::ensure_sdk_authenticated(),
ModelBackend::Pi => crate::pi_sdk::ensure_pi_authenticated(&self.model.canonical()),
ModelBackend::Codex => crate::codex_sdk::ensure_codex_authenticated(),
}
}
pub async fn start_coder_session(
&mut self,
cwd: &Path,
) -> Result<CoderSessionEnsure, AgentError> {
if self.session_header.is_none() {
return Err(AgentError(
"start_coder_session requires bind_session_header so a header is always sent"
.into(),
));
}
let ensure = self.ensure_coder_session(cwd).await?;
self.deliver_session_header_if_needed().await?;
Ok(ensure)
}
pub async fn ensure_coder_session(
&mut self,
cwd: &Path,
) -> Result<CoderSessionEnsure, AgentError> {
if sdk_bridge_needs_restart(self) {
self.end_coder_session().await?;
}
if self.has_open_coder_session() {
return Ok(CoderSessionEnsure::Reused);
}
let resumed = begin_coder_session_resumed(self, cwd).await?;
Ok(if resumed {
CoderSessionEnsure::Reused
} else {
CoderSessionEnsure::Fresh
})
}
pub async fn begin_coder_session(&mut self, cwd: &Path) -> Result<(), AgentError> {
begin_coder_session_resumed(self, cwd).await.map(|_| ())
}
pub(crate) async fn deliver_session_header_if_needed(&mut self) -> Result<(), AgentError> {
super::sdk_client_session_header::send_bound_session_header(self).await
}
pub async fn end_coder_session(&mut self) -> Result<(), AgentError> {
let Some(home) = self.coder.as_mut() else {
return Ok(());
};
let Some(s) = home.take_live_session() else {
return Ok(());
};
if matches!(self.model.backend, ModelBackend::Cursor) {
spawn::remember_agent_id_from(self, &s);
}
s.shutdown().await?;
Ok(())
}
}
#[must_use]
pub(crate) fn sdk_bridge_needs_restart(client: &SdkClient) -> bool {
super::sdk_client::live_session(client)
.is_some_and(|s| s.started_at.elapsed() >= SDK_BRIDGE_MAX_AGE)
}
async fn begin_coder_session_resumed(
client: &mut SdkClient,
cwd: &Path,
) -> Result<bool, AgentError> {
reject_no_force(client)?;
if client.has_open_coder_session() {
return Err(AgentError(format!(
"{} SDK session is already open",
client.model.backend.label()
)));
}
let cwd = crate::acp::resolve_acp_session_cwd(cwd)?;
let thinking = spawn::spawn_thinking_wire(client);
spawn::spawn_with_retries(client, cwd, thinking.as_deref()).await
}
fn reject_no_force(client: &SdkClient) -> Result<(), AgentError> {
crate::acp::require_force(client.io.force)
}
#[cfg(test)]
#[path = "sdk_client_session_tests.rs"]
mod sdk_client_session_tests;