use serde::{Deserialize, Serialize};
pub use crate::language::ast::*;
#[derive(Clone, Debug, PartialEq)]
pub enum Token {
Ident(String),
Str(String),
Num(f64),
LBrace,
RBrace,
LBracket,
RBracket,
Arrow,
Comma,
Eof,
}
impl Token {
pub fn describe(&self) -> String {
match self {
Token::Ident(s) => format!("identifier `{s}`"),
Token::Str(_) => "string".to_string(),
Token::Num(_) => "number".to_string(),
Token::LBrace => "`{`".to_string(),
Token::RBrace => "`}`".to_string(),
Token::LBracket => "`[`".to_string(),
Token::RBracket => "`]`".to_string(),
Token::Arrow => "`->`".to_string(),
Token::Comma => "`,`".to_string(),
Token::Eof => "end of input".to_string(),
}
}
}
pub use crate::language::span::Span;
#[derive(Clone, Debug, PartialEq)]
pub struct SpannedToken {
pub token: Token,
pub span: Span,
}
pub const END: &str = "END";
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Blueprint {
pub graph_id: String,
pub start: String,
pub channels: Vec<ChannelSpec>,
pub nodes: Vec<NodeSpec>,
pub edges: Vec<EdgeSpec>,
pub defaults: Vec<(String, Literal)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub input: Vec<IoFieldSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub output: Vec<IoFieldSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checkpoint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub interrupt: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub joins: Vec<JoinSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provenance: Option<BlueprintProvenance>,
}
impl Blueprint {
pub fn provenance(&self) -> Option<&BlueprintProvenance> {
self.provenance.as_ref()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum Origin {
File(String),
Generated(Option<String>),
}
impl Origin {
pub fn file(path: impl Into<String>) -> Self {
Origin::File(path.into())
}
pub fn generated() -> Self {
Origin::Generated(None)
}
pub fn generated_by(label: impl Into<String>) -> Self {
Origin::Generated(Some(label.into()))
}
pub fn as_display(&self) -> String {
match self {
Origin::File(path) => path.clone(),
Origin::Generated(None) => "generated".to_string(),
Origin::Generated(Some(label)) => format!("generated by {label}"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct NamedSpan {
pub name: String,
pub span: Span,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EdgeSpan {
pub from: String,
pub to: String,
pub span: Span,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlueprintProvenance {
pub origin: Origin,
pub graph: Span,
pub nodes: Vec<NamedSpan>,
pub channels: Vec<NamedSpan>,
pub edges: Vec<EdgeSpan>,
}
impl BlueprintProvenance {
pub fn node_span(&self, name: &str) -> Option<Span> {
self.nodes.iter().find(|n| n.name == name).map(|n| n.span)
}
pub fn channel_span(&self, name: &str) -> Option<Span> {
self.channels
.iter()
.find(|c| c.name == name)
.map(|c| c.span)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ChannelSpec {
pub name: String,
pub reducer: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub args: Vec<Literal>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct IoFieldSpec {
pub name: String,
pub ty: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct JoinSpec {
pub sources: Vec<String>,
pub target: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct CommandSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub goto: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub update: Vec<(String, Literal)>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SendSpec {
pub target: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct NodeSpec {
pub name: String,
pub kind: String,
pub model: Option<String>,
pub prompt: Option<String>,
pub tools: Vec<String>,
pub routing: Routing,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subgraph: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub script: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub command: Option<CommandSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub sends: Vec<SendSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub join_sources: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub options: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checkpoint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub retry: Vec<(String, Literal)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub metadata: Vec<(String, Literal)>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum Routing {
Next(String),
Conditional(Vec<(String, String)>),
Terminal,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct EdgeSpec {
pub from: String,
pub to: String,
}