use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use crate::agent_session::{AgentSession, RetrySettings};
use crate::orchestration::SessionRuntime;
use theway_core::{AgentHarness, AgentRunError};
use theway_llm_provider::{ImageContent, InputModality};
pub type TurnFut = Pin<Box<dyn Future<Output = Result<Option<String>, AgentRunError>>>>;
#[derive(Default)]
pub struct TurnState {
pub fut: Option<TurnFut>,
pub aborted: bool,
pub prefix: &'static str,
}
pub async fn poll_turn(fut: &mut Option<TurnFut>) -> Result<Option<String>, AgentRunError> {
fut.as_mut().expect("turn future present").await
}
pub enum QueuedTurn {
UserPrompt {
display: String,
prompt: String,
images: Vec<ImageContent>,
},
AgentPrompt {
display: String,
prompt: String,
error_context: &'static str,
},
PromptTemplate {
display: String,
name: String,
vars: serde_json::Map<String, serde_json::Value>,
},
Compaction {
display: String,
custom: Option<String>,
},
}
impl QueuedTurn {
pub fn display(&self) -> &str {
match self {
Self::UserPrompt { display, .. }
| Self::AgentPrompt { display, .. }
| Self::PromptTemplate { display, .. }
| Self::Compaction { display, .. } => display,
}
}
}
#[derive(Clone)]
pub struct ReplKernel {
harness: Arc<AgentHarness>,
trigger_executor: Arc<crate::trigger_engine::execution::TriggerExecutor>,
retry: RetrySettings,
extension_host: Option<Arc<crate::ts_extensions::SessionPluginHost>>,
}
impl ReplKernel {
pub fn new(
harness: Arc<AgentHarness>,
trigger_executor: Arc<crate::trigger_engine::execution::TriggerExecutor>,
retry: RetrySettings,
) -> Self {
Self {
harness,
trigger_executor,
retry,
extension_host: None,
}
}
pub fn set_extension_host(
&mut self,
extension_host: Option<Arc<crate::ts_extensions::SessionPluginHost>>,
) {
self.extension_host = extension_host;
}
pub fn extension_host(&self) -> Option<&Arc<crate::ts_extensions::SessionPluginHost>> {
self.extension_host.as_ref()
}
pub fn trigger_executor(&self) -> &Arc<crate::trigger_engine::execution::TriggerExecutor> {
&self.trigger_executor
}
pub fn harness(&self) -> &Arc<AgentHarness> {
&self.harness
}
#[allow(dead_code)] pub fn replace_runtime(&mut self, runtime: SessionRuntime) {
self.harness = runtime.harness;
self.trigger_executor = runtime.trigger_executor;
self.extension_host = runtime.extension_host;
}
pub fn abort(&self) {
self.harness.abort();
}
pub fn is_streaming(&self) -> bool {
self.harness.agent().is_streaming()
}
pub fn current_model_accepts_images(&self) -> bool {
let state = self.harness.agent().state();
state
.model
.as_ref()
.map(|model| model.input.contains(&InputModality::Image))
.unwrap_or(false)
}
pub fn prompt_turn(&self, prompt: String) -> TurnFut {
let harness = self.harness.clone();
Box::pin(async move { harness.prompt(prompt).await.map(|_| None) })
}
pub fn user_prompt_turn(
&self,
prompt_text: String,
loaded_images: Vec<ImageContent>,
) -> TurnFut {
let harness = self.harness.clone();
let retry = self.retry.clone();
let has_images = !loaded_images.is_empty();
Box::pin(async move {
if has_images {
harness
.prompt_with_images(prompt_text, loaded_images)
.await
.map(|_| None)
} else {
AgentSession::new(harness, retry)
.prompt(prompt_text)
.await
.map(|_| None)
}
})
}
pub fn template_turn(
&self,
name: String,
vars: serde_json::Map<String, serde_json::Value>,
) -> TurnFut {
let harness = self.harness.clone();
Box::pin(async move {
harness
.prompt_from_template(&name, vars)
.await
.map(|_| None)
})
}
pub fn compaction_turn(&self, custom: Option<String>) -> TurnFut {
let harness = self.harness.clone();
Box::pin(async move {
harness.force_compact(custom).await.map(|ran| {
Some(if ran {
"compaction ran".to_string()
} else {
"nothing to compact".to_string()
})
})
})
}
pub fn continue_turn(&self) -> TurnFut {
let harness = self.harness.clone();
Box::pin(async move { harness.continue_().await.map(|_| None) })
}
}
#[cfg(test)]
tests_bridge_macro::tests_bridge!("turn/kernel");
#[cfg(test)]
mod kernel_extra_tests {
tests_bridge_macro::tests_bridge!("turn/kernel/extra");
}