use std::sync::{Arc, atomic::AtomicU8};
pub use kcode_k1_chat_chatend::{
ActionId, BoxContent, BoxId, ChatBox, DispatchCall, DispatchOutcome, TransitionError,
};
#[derive(Clone, Debug)]
pub struct InferenceStart {
pub job: u64,
pub frontier: Option<BoxId>,
pub attempt: Arc<AtomicU8>,
}
#[derive(Debug)]
pub enum StateError {
Transition(TransitionError),
WrongInference { expected: Option<u64>, actual: u64 },
JobIdExhausted,
NotStalled,
Busy,
}
impl From<TransitionError> for StateError {
fn from(error: TransitionError) -> Self {
Self::Transition(error)
}
}
struct ActiveInference {
job: u64,
frontier: Option<BoxId>,
_attempt: Arc<AtomicU8>,
}
struct RetryRound {
frontier: Option<BoxId>,
ready: bool,
}
pub struct ActorState {
chatend: kcode_k1_chat_chatend::Chatend,
next_job: u64,
active: Option<ActiveInference>,
retry: Option<RetryRound>,
dispatch: bool,
scheduled: bool,
halt: Option<String>,
}
impl ActorState {
pub fn new(force: bool) -> Self {
Self {
chatend: kcode_k1_chat_chatend::Chatend::new(),
next_job: 0,
active: None,
retry: None,
dispatch: false,
scheduled: force,
halt: None,
}
}
pub fn boxes(&self) -> &[ChatBox] {
self.chatend.boxes()
}
pub fn halted(&self) -> bool {
self.halt.is_some() || self.retry.is_some()
}
pub fn halt(&mut self, text: String) -> bool {
if self.halted() {
false
} else {
self.halt = Some(text);
true
}
}
pub fn take_halt(&mut self) -> Option<String> {
self.halt.take()
}
pub fn restart(&mut self) -> Result<(), StateError> {
if !self.halted() {
return Err(StateError::NotStalled);
}
if self.active.is_some() || self.dispatch {
return Err(StateError::Busy);
}
self.halt = None;
if let Some(retry) = &mut self.retry {
retry.ready = true;
} else {
self.scheduled = true;
}
Ok(())
}
pub fn accept_system(&mut self, text: String) -> Result<(), StateError> {
self.chatend.accept_system(text)?;
self.scheduled = true;
Ok(())
}
pub fn accept_user(&mut self, text: String) -> Result<(), StateError> {
self.chatend.accept_user(text)?;
self.scheduled = true;
Ok(())
}
pub fn accept_attachment(&mut self) -> Result<(), StateError> {
self.chatend.accept_attachment()?;
self.scheduled = true;
Ok(())
}
pub fn accept_async_return(
&mut self,
action_id: ActionId,
result: Result<String, String>,
) -> Result<(), StateError> {
self.chatend.accept_async_return(action_id, result)?;
self.scheduled = true;
Ok(())
}
pub fn force_inference(&mut self) {
self.scheduled = true;
}
pub fn begin_inference(&mut self) -> Result<Option<InferenceStart>, StateError> {
if self.halt.is_some() || self.active.is_some() || self.dispatch {
return Ok(None);
}
if let Some(retry) = &self.retry {
if !retry.ready {
return Ok(None);
}
let frontier = retry.frontier;
let start = self.activate(frontier)?;
self.retry = None;
return Ok(Some(start));
}
if !self.scheduled {
return Ok(None);
}
let job = self.next_job()?;
let frontier = self.boxes().last().map(ChatBox::id);
self.chatend.start_round()?;
self.next_job = job;
self.scheduled = false;
Ok(Some(self.install_active(job, frontier)))
}
pub fn append_kennedy_text(&mut self, job: u64, text: &str) -> Result<(), StateError> {
self.require_job(job)?;
self.chatend.append_kennedy_text(text)?;
Ok(())
}
pub fn collect_provider_call(
&mut self,
job: u64,
name: String,
arguments: String,
) -> Result<(), StateError> {
self.require_job(job)?;
self.chatend.collect_provider_call(name, arguments)?;
Ok(())
}
pub fn complete_provider_output(
&mut self,
job: u64,
action_ids: Vec<ActionId>,
) -> Result<Vec<DispatchCall>, StateError> {
self.require_job(job)?;
let calls = self.chatend.complete_provider_output(&action_ids)?;
self.active = None;
self.dispatch = !calls.is_empty();
Ok(calls)
}
pub fn complete_dispatch(&mut self, outcomes: Vec<DispatchOutcome>) -> Result<(), StateError> {
self.chatend.complete_dispatch(outcomes)?;
self.dispatch = false;
Ok(())
}
pub fn stall_inference(&mut self, job: u64, text: String) -> Result<(), StateError> {
self.require_job(job)?;
let active = self.active.take().expect("validated active inference");
self.halt(text);
self.retry = Some(RetryRound {
frontier: active.frontier,
ready: false,
});
Ok(())
}
pub fn quiet(&self) -> bool {
self.active.is_none() && self.retry.is_none() && !self.dispatch && !self.scheduled
}
fn activate(&mut self, frontier: Option<BoxId>) -> Result<InferenceStart, StateError> {
let job = self.next_job()?;
self.next_job = job;
Ok(self.install_active(job, frontier))
}
fn install_active(&mut self, job: u64, frontier: Option<BoxId>) -> InferenceStart {
let attempt = Arc::new(AtomicU8::new(1));
self.active = Some(ActiveInference {
job,
frontier,
_attempt: attempt.clone(),
});
InferenceStart {
job,
frontier,
attempt,
}
}
fn next_job(&self) -> Result<u64, StateError> {
self.next_job
.checked_add(1)
.ok_or(StateError::JobIdExhausted)
}
fn require_job(&self, job: u64) -> Result<(), StateError> {
let expected = self.active.as_ref().map(|active| active.job);
if expected == Some(job) {
Ok(())
} else {
Err(StateError::WrongInference {
expected,
actual: job,
})
}
}
}
#[cfg(test)]
mod tests;