use crate::types::{Ask, DecisionRecord, JournalKey, RunOutcome};
use serde_json::Value;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum NodeState {
Waiting,
Ready,
Dispatched,
AwaitingClient,
DoneOk,
DoneFailed,
Dead,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum EdgeRes {
Fired,
Dead,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct Spent {
pub supersteps: u64,
pub input_tokens: u64,
pub output_tokens: u64,
pub usd_micros: u64,
pub wall_ms: u64,
pub storage_bytes: u64,
pub journal_grains: u64,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Phase {
Idle,
Open {
outstanding: BTreeSet<JournalKey>,
results: BTreeMap<usize, Value>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
send_results: BTreeMap<String, Value>,
record: DecisionRecord,
},
Finished(RunOutcome),
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SendTask {
pub node_idx: usize,
pub input: Value,
pub attempt: u32,
pub state: SendTaskState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SendTaskState {
Queued,
Running,
Done,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PendingAsk {
pub key: JournalKey,
pub node_idx: usize,
pub ask: Ask,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PendingToolCall {
pub model_call_id: String,
pub tool_name: String,
pub arguments: Value,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum FlowNeed {
NextTurn,
Tools(Vec<PendingToolCall>),
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct AbstractFlow {
pub messages: Vec<Value>,
pub next_effect_seq: u32,
pub pending_tools: BTreeMap<u32, PendingToolCall>,
pub round_results: BTreeMap<u32, Value>,
pub need: Option<FlowNeed>,
pub unknown_strikes: u32,
}
fn is_zero(n: &u64) -> bool {
*n == 0
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SchedulerState {
pub run_id: String,
pub superstep: u64,
pub phase: Phase,
pub node_gen: Vec<u32>,
pub node_state: Vec<NodeState>,
pub in_res: Vec<BTreeMap<usize, EdgeRes>>,
pub edge_fired: Vec<u32>,
pub attempt: Vec<u32>,
pub attempt_base: Vec<u32>,
pub context: Value,
pub spent: Spent,
pub elapsed_ms: u64,
pub clock_ms: u64,
pub wall_open: Option<u64>,
pub paused_at: Option<u64>,
pub pending_asks: BTreeMap<String, PendingAsk>,
pub abstract_flows: BTreeMap<String, AbstractFlow>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub send_tasks: BTreeMap<String, SendTask>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub spawn_counter: BTreeMap<String, u32>,
pub announced_asks: BTreeSet<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub inbox: Vec<Value>,
#[serde(default, skip_serializing_if = "is_zero")]
pub inputs_seen: u64,
pub cancel: Option<(String, String)>,
pub exhausted: Option<crate::error::BudgetAxis>,
pub failed: Option<(usize, String)>,
}
impl SchedulerState {
pub fn new(run_id: &str, plan: &crate::plan::PlanGraph) -> Self {
let n = plan.nodes.len();
SchedulerState {
run_id: run_id.to_string(),
superstep: 0,
phase: Phase::Idle,
node_gen: vec![0; n],
node_state: vec![NodeState::Waiting; n],
in_res: vec![BTreeMap::new(); n],
edge_fired: vec![0; plan.edges.len()],
attempt: vec![0; n],
attempt_base: vec![0; n],
context: Value::Object(serde_json::Map::new()),
spent: Spent::default(),
elapsed_ms: 0,
clock_ms: 0,
wall_open: None,
paused_at: None,
pending_asks: BTreeMap::new(),
abstract_flows: BTreeMap::new(),
send_tasks: BTreeMap::new(),
spawn_counter: BTreeMap::new(),
announced_asks: BTreeSet::new(),
inbox: Vec::new(),
inputs_seen: 0,
cancel: None,
exhausted: None,
failed: None,
}
}
pub fn is_terminal(&self) -> bool {
matches!(self.phase, Phase::Finished(_))
}
pub fn outcome(&self) -> Option<&RunOutcome> {
match &self.phase {
Phase::Finished(o) => Some(o),
_ => None,
}
}
}