use crate::command::NodeOutput;
use crate::config::GraphConfig;
use crate::error::AgentGraphError;
use crate::node::Node;
use crate::state::AgentState;
use serde_json::Value;
pub type MergeFn = Box<dyn Fn(Vec<(String, Value)>) -> crate::Result<Value> + Send + Sync>;
pub struct JoinNode {
name: Option<String>,
input_keys: Vec<String>,
output_key: String,
merge_fn: MergeFn,
}
impl JoinNode {
pub fn new(
input_keys: Vec<String>,
output_key: impl Into<String>,
merge_fn: impl Fn(Vec<(String, Value)>) -> crate::Result<Value> + Send + Sync + 'static,
) -> Self {
Self {
name: None,
input_keys,
output_key: output_key.into(),
merge_fn: Box::new(merge_fn),
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn collect_array(input_keys: Vec<String>, output_key: impl Into<String>) -> Self {
Self::new(input_keys, output_key, |values| {
let arr: Vec<Value> = values.into_iter().map(|(_, v)| v).collect();
Ok(Value::Array(arr))
})
}
pub fn merge_objects(input_keys: Vec<String>, output_key: impl Into<String>) -> Self {
Self::new(input_keys, output_key, |values| {
let mut result = serde_json::Map::new();
for (key, value) in values {
if let Value::Object(map) = value {
for (k, v) in map {
result.insert(k, v);
}
} else {
result.insert(key, value);
}
}
Ok(Value::Object(result))
})
}
}
#[async_trait::async_trait]
impl Node for JoinNode {
async fn execute(
&self,
state: &AgentState,
_config: &GraphConfig,
) -> crate::Result<NodeOutput> {
let mut inputs = Vec::new();
for key in &self.input_keys {
let value: Value = state.get_opt::<Value>(key).await?.unwrap_or(Value::Null);
inputs.push((key.clone(), value));
}
let has_data = inputs.iter().any(|(_, v)| !v.is_null());
if !has_data {
return Err(AgentGraphError::ExecutionError(format!(
"JoinNode: no data found for input keys {:?}",
self.input_keys
)));
}
let merged = (self.merge_fn)(inputs)?;
state.set_raw(&self.output_key, merged).await?;
Ok(NodeOutput::Done)
}
fn name(&self) -> Option<&str> {
self.name.as_deref()
}
}
impl std::fmt::Debug for JoinNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JoinNode")
.field("name", &self.name)
.field("input_keys", &self.input_keys)
.field("output_key", &self.output_key)
.finish()
}
}