use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::error::{Result, SubstrateError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TaskState {
Submitted,
Working,
InputRequired,
Completed,
Failed,
Cancelled,
}
impl TaskState {
pub fn is_terminal(self) -> bool {
matches!(
self,
TaskState::Completed | TaskState::Failed | TaskState::Cancelled
)
}
pub fn can_transition(from: TaskState, to: TaskState) -> bool {
use TaskState::*;
if from == to {
return false;
}
if from.is_terminal() {
return false;
}
if matches!(to, Failed | Cancelled) {
return true;
}
matches!(
(from, to),
(Submitted, Working)
| (Working, InputRequired)
| (Working, Completed)
| (InputRequired, Working)
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Task {
pub id: Uuid,
pub prompt: String,
pub cwd: String,
pub state: TaskState,
pub parent_task_id: Option<Uuid>,
pub requirement_id: Option<String>,
pub epic_id: Option<String>,
#[serde(default)]
pub conv_id: Option<String>,
}
impl Task {
pub fn new(prompt: impl Into<String>, cwd: impl Into<String>) -> Self {
Task {
id: Uuid::new_v4(),
prompt: prompt.into(),
cwd: cwd.into(),
state: TaskState::Submitted,
parent_task_id: None,
requirement_id: None,
epic_id: None,
conv_id: None,
}
}
pub fn advance(&mut self, to: TaskState) -> Result<()> {
if TaskState::can_transition(self.state, to) {
self.state = to;
Ok(())
} else {
Err(SubstrateError::InvalidTransition {
from: self.state,
to,
})
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentRole {
Lead,
Teammate,
Subagent,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Agent {
pub id: Uuid,
pub name: String,
pub role: AgentRole,
pub engine: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Team {
pub id: Uuid,
pub name: String,
pub lead: Uuid,
pub members: Vec<Uuid>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MessageKind {
Task,
Reply,
Question,
Status,
Artifact,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Part {
Text {
text: String,
},
Artifact {
name: String,
uri: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Message {
pub id: Uuid,
pub from: String,
pub to: String,
pub kind: MessageKind,
pub parts: Vec<Part>,
pub in_reply_to: Option<Uuid>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Mailbox {
pub owner: String,
pub messages: Vec<Message>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Conversation {
pub id: String,
pub messages: Vec<Message>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StructuredResult {
pub text: String,
pub artifacts: Vec<Part>,
pub pr_urls: Vec<String>,
pub status: TaskState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct EngineCapabilities {
pub supports_resume: bool,
pub supports_subagents: bool,
pub supports_mcp_import: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConversationDump {
pub conversation_id: String,
pub raw: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Session {
pub conv_id: String,
pub pid: Option<u32>,
pub logfile: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoutingDecision {
pub engine: String,
pub model: String,
#[serde(default)]
pub reason: Option<String>,
}
impl RoutingDecision {
pub fn default_forge_kimi() -> Self {
RoutingDecision {
engine: "forge".to_string(),
model: "accounts/fireworks/routers/kimi-k2p6-turbo".to_string(),
reason: Some("phase1-default".to_string()),
}
}
}