use std::collections::{BTreeMap, HashMap};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum NodeAction {
LlmInfer {
system_prompt_override: Option<String>,
#[serde(default)]
use_streaming: bool,
#[serde(default)]
include_tool_definitions: bool,
#[serde(default)]
tool_filter_override: Option<Vec<String>>,
#[serde(default)]
thinking_budget: Option<u32>,
#[serde(default)]
temperature: Option<f32>,
#[serde(default)]
max_tokens: Option<u32>,
},
ToolCall {
tool_name: String,
args_template: Option<String>,
},
Delegate {
agent_kind: String,
task_template: String,
},
HumanApproval {
description: String,
},
ConditionCheck {
condition: String,
},
SwitchParadigm {
paradigm: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum EdgeCondition {
HasToolCalls,
IsFinalAnswer,
RequestsDelegation,
ErrorOccurred,
StateEquals { variable: String, value: String },
Always,
Custom { name: String, description: String },
ParadigmEquals {
paradigm: String,
},
IterationExceeds {
count: usize,
},
}
impl EdgeCondition {
pub fn is_unconditional(&self) -> bool {
matches!(self, Self::Always)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphNode {
pub id: String,
pub action: NodeAction,
#[serde(default)]
pub interrupt: bool,
#[serde(default)]
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphEdge {
pub from: String,
pub to: String,
#[serde(default)]
pub condition: Option<EdgeCondition>,
#[serde(default)]
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateGraph {
pub name: String,
pub nodes: BTreeMap<String, GraphNode>,
pub edges: BTreeMap<String, Vec<GraphEdge>>,
pub entry_point: String,
#[serde(default)]
pub terminal_nodes: Vec<String>,
}
impl StateGraph {
pub fn new(name: impl Into<String>, entry_point: impl Into<String>) -> Self {
Self {
name: name.into(),
nodes: BTreeMap::new(),
edges: BTreeMap::new(),
entry_point: entry_point.into(),
terminal_nodes: Vec::new(),
}
}
pub fn add_node(&mut self, node: GraphNode) {
self.nodes.insert(node.id.clone(), node);
}
pub fn add_edge(&mut self, edge: GraphEdge) {
self.edges.entry(edge.from.clone()).or_default().push(edge);
}
pub fn add_terminal(&mut self, node_id: impl Into<String>) {
self.terminal_nodes.push(node_id.into());
}
pub fn get_node(&self, id: &str) -> Option<&GraphNode> {
self.nodes.get(id)
}
pub fn get_edges_from(&self, node_id: &str) -> Vec<&GraphEdge> {
self.edges
.get(node_id)
.map(|edges| edges.iter().collect())
.unwrap_or_default()
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn edge_count(&self) -> usize {
self.edges.values().map(|v| v.len()).sum()
}
pub fn has_cycles(&self) -> bool {
let mut visited = std::collections::HashSet::new();
let mut recursion_stack = std::collections::HashSet::new();
fn dfs(
node_id: &str,
graph: &StateGraph,
visited: &mut std::collections::HashSet<String>,
recursion_stack: &mut std::collections::HashSet<String>,
) -> bool {
visited.insert(node_id.to_string());
recursion_stack.insert(node_id.to_string());
for edge in graph.get_edges_from(node_id) {
if !visited.contains(&edge.to) {
if dfs(&edge.to, graph, visited, recursion_stack) {
return true;
}
} else if recursion_stack.contains(&edge.to) {
return true; }
}
recursion_stack.remove(node_id);
false
}
for node_id in self.nodes.keys() {
if !visited.contains(node_id) && dfs(node_id, self, &mut visited, &mut recursion_stack)
{
return true;
}
}
false
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphState {
pub conversation: oneai_core::Conversation,
pub variables: HashMap<String, String>,
pub last_result: Option<String>,
pub last_error: Option<String>,
pub should_terminate: bool,
#[serde(default)]
pub metadata: HashMap<String, String>,
#[serde(default)]
pub parsed_decision: Option<oneai_core::GraphDecision>,
#[serde(default)]
pub active_paradigm: Option<String>,
#[serde(default)]
pub iteration_count: usize,
#[serde(default)]
pub token_budget_remaining: u32,
#[serde(default)]
pub interrupt_points: Vec<oneai_core::InterruptPoint>,
}
impl GraphState {
pub fn new() -> Self {
Self {
conversation: oneai_core::Conversation::new(),
variables: HashMap::new(),
last_result: None,
last_error: None,
should_terminate: false,
metadata: HashMap::new(),
parsed_decision: None,
active_paradigm: None,
iteration_count: 0,
token_budget_remaining: 0, interrupt_points: Vec::new(),
}
}
pub fn with_budget(budget: u32) -> Self {
Self {
token_budget_remaining: budget,
..Self::new()
}
}
}
impl Default for GraphState {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphExecutionResult {
pub name: String,
pub final_state: GraphState,
pub completed: bool,
pub terminal_node: Option<String>,
pub iterations: usize,
pub interrupt_checkpoints: Vec<String>,
}