use super::{Publish, Pump};
use serde_json::{Value, json};
pub(super) enum Opening {
List,
New,
Open { previous: String },
}
impl Pump {
pub(super) fn session_control(&mut self, change: &Value) -> anyhow::Result<()> {
if self.opening.is_some() {
return self.say(json!({"type":"session_failed","reason":"a conversation change is already in flight"}));
}
let opening = if change.get("list").is_some() {
Opening::List
} else if change.get("new").is_some() {
Opening::New
} else if let Some(id) = change["open"].as_str().filter(|id| !id.is_empty()) {
Opening::Open {
previous: id.to_owned(),
}
} else {
return self
.say(json!({"type":"session_failed","reason":"session takes list, new or open"}));
};
if !matches!(opening, Opening::List) && self.turn.is_some() {
return self.say(
json!({"type":"session_failed","reason":"a turn is open; cancel it or wait"}),
);
}
let (method, params) = match &opening {
Opening::List => ("session/list", json!({"cwd": self.session.cwd})),
Opening::New => super::super::handshake::session_request(
self.session.version,
&self.session.cwd,
&self.session.seat,
None,
)?,
Opening::Open { previous: id } => super::super::handshake::session_request(
self.session.version,
&self.session.cwd,
&self.session.seat,
Some(id),
)?,
};
let opening = match opening {
Opening::Open { previous: id } => Opening::Open {
previous: std::mem::replace(&mut self.session.id, id),
},
other => other,
};
if !matches!(opening, Opening::List) {
self.say(json!({"type":"session_opening"}))?;
}
let id = self.next_id;
self.next_id = id
.checked_add(1)
.ok_or_else(|| anyhow::anyhow!("ACP request id exhausted"))?;
self.opening = Some((id, opening));
self.output.request(id, method, params)
}
pub(super) fn opened(&mut self, result: Result<Value, i64>) -> anyhow::Result<()> {
let Some((_, opening)) = self.opening.take() else {
return Ok(());
};
let answer = match result {
Ok(answer) => answer,
Err(code) => {
if let Opening::Open { previous } = opening {
self.session.id = previous;
}
return self.say(json!({"type":"session_failed","code":code}));
}
};
match opening {
Opening::List => self.say(json!({
"type": "sessions",
"current": self.session.id,
"sessions": listed(&answer, self.listing),
})),
Opening::New | Opening::Open { .. } => {
let resumed = matches!(opening, Opening::Open { .. });
if let Some(id) = answer["sessionId"].as_str().filter(|_| !resumed) {
id.clone_into(&mut self.session.id);
}
if let Some(options) = answer
.get("configOptions")
.filter(|options| options.is_array())
{
self.session.config = options.clone();
}
self.say(
json!({"type":"session_opened","session":self.session.id,"resumed":resumed}),
)?;
self.say(json!({"type":"config_options","options":self.session.config}))
}
}
}
pub(super) const fn replaying(&self) -> bool {
matches!(self.opening, Some((_, Opening::Open { .. })))
}
pub(super) fn foreign(&self, params: &Value) -> bool {
self.opening.is_some()
&& params["sessionId"]
.as_str()
.is_some_and(|id| id != self.session.id)
}
pub(super) fn prompt_text(&self, value: &Value) -> anyhow::Result<Option<String>> {
let Some(command) = value.get("command") else {
return self.exchange.inbox(value);
};
let name = command["name"]
.as_str()
.filter(|name| !name.is_empty() && !name.contains(char::is_whitespace))
.ok_or_else(|| anyhow::anyhow!("command has no name"))?;
Ok(Some(
command["input"]
.as_str()
.filter(|input| !input.is_empty())
.map_or_else(|| format!("/{name}"), |input| format!("/{name} {input}")),
))
}
fn say(&self, item: Value) -> anyhow::Result<()> {
self.publications
.try_send(Publish::Stream(item))
.map_err(|_| anyhow::anyhow!("stream_queue reached or publisher closed"))
}
}
fn listed(answer: &Value, (count, title): (usize, usize)) -> Vec<Value> {
let mut sessions: Vec<&Value> = answer["sessions"]
.as_array()
.map(|all| all.iter().collect())
.unwrap_or_default();
sessions.sort_by(|left, right| right["updatedAt"].as_str().cmp(&left["updatedAt"].as_str()));
sessions
.into_iter()
.take(count)
.filter_map(|session| {
let id = session["sessionId"].as_str()?;
let kept: String = session["title"]
.as_str()
.unwrap_or_default()
.chars()
.take(title)
.collect();
Some(json!({"id": id, "title": kept, "updated_at": session["updatedAt"]}))
})
.collect()
}