use crate::frontend::{FrontendRuntime, FrontendTurnState, HttpFrontendRuntime};
use crate::live_runtime::{list_live_runtimes, resolve_live_runtime, LiveRuntimeRecord};
pub fn controlled_runtime(harness: &str, session_id: &str) -> Option<LiveRuntimeRecord> {
list_live_runtimes().ok()?.into_iter().find(|record| {
record.source.harness == harness
&& (record.source.session_id == session_id || record.runtime_session_id == session_id)
})
}
pub fn controlled_runtimes() -> Vec<LiveRuntimeRecord> {
list_live_runtimes().unwrap_or_default()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuntimeDelivery {
Steered,
Started,
NotWoken,
}
pub async fn runtime_turn_state(record: &LiveRuntimeRecord) -> Result<FrontendTurnState, String> {
let remote = connect(record).await?;
FrontendRuntime::describe(remote.as_ref())
.await
.map(|descriptor| descriptor.turn_state)
.map_err(|error| error.to_string())
}
pub async fn deliver_to_runtime(
record: &LiveRuntimeRecord,
text: String,
wake: bool,
) -> Result<RuntimeDelivery, String> {
let remote = connect(record).await?;
let descriptor = FrontendRuntime::describe(remote.as_ref())
.await
.map_err(|error| error.to_string())?;
if descriptor.session_id != record.runtime_session_id {
return Err("the live runtime's identity did not match its receipt".into());
}
match descriptor.turn_state {
FrontendTurnState::Busy if descriptor.actions.steer => {
FrontendRuntime::steer(remote.as_ref(), text)
.await
.map_err(|error| error.to_string())?;
Ok(RuntimeDelivery::Steered)
}
FrontendTurnState::Busy => Err(
"a turn is running and this runtime does not accept steering; the message was not \
delivered into it"
.into(),
),
FrontendTurnState::Idle if wake => {
FrontendRuntime::send_input_with_images(remote.clone(), text, Vec::new())
.await
.map_err(|error| error.to_string())?;
Ok(RuntimeDelivery::Started)
}
FrontendTurnState::Idle => Ok(RuntimeDelivery::NotWoken),
}
}
pub fn answer_to(
homes: &crate::HarnessHomes,
record: &LiveRuntimeRecord,
message_id: &str,
) -> Option<String> {
let query = crate::DiscoveryQuery {
harnesses: vec![crate::HarnessId::new(record.source.harness.clone())],
homes: homes.clone(),
workspace: Some(record.source.workspace.clone()),
..Default::default()
};
let descriptor = crate::discover_sessions(&query)
.ok()?
.into_iter()
.find(|descriptor| {
descriptor.locator.session_id == record.source.session_id
|| descriptor.locator.session_id == record.runtime_session_id
})?;
let session =
crate::sdk::load_session_with_fidelity(&descriptor.locator, crate::Fidelity::Semantic)
.ok()?;
let text = |message: &crate::ChatMessage| message.content.clone().unwrap_or_default();
let asked = session.messages.iter().rposition(|message| {
message.role == crate::Role::User && text(message).contains(message_id)
})?;
session.messages[asked + 1..]
.iter()
.rev()
.find(|message| message.role == crate::Role::Assistant && !text(message).trim().is_empty())
.map(text)
}
async fn connect(
record: &LiveRuntimeRecord,
) -> Result<std::sync::Arc<HttpFrontendRuntime>, String> {
let receipt = resolve_live_runtime(&record.endpoint, &record.source)
.map_err(|error| error.to_string())?;
HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
.await
.map_err(|error| error.to_string())
}