use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(default)]
pub struct AgentsListFilter {
pub active_only: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub plugin_filter: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenant_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AgentSummary {
pub id: String,
pub active: bool,
pub model_provider: String,
pub bindings_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AgentsListResponse {
pub agents: Vec<AgentSummary>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AgentsGetParams {
pub agent_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AgentDetail {
pub id: String,
pub model: ModelRef,
pub active: bool,
pub allowed_tools: Vec<String>,
pub inbound_bindings: Vec<BindingSummary>,
pub system_prompt: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub workspace: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub extra_docs: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub heartbeat: Option<HeartbeatWire>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HeartbeatWire {
pub enabled: bool,
pub interval: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ModelRef {
pub provider: String,
pub model: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BindingSummary {
pub plugin: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub instance: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AgentUpsertInput {
pub id: String,
pub model: ModelRef,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_tools: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub inbound_bindings: Option<Vec<BindingSummary>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_prompt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub active: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transcripts_dir: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub workspace: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extra_docs: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub heartbeat: Option<HeartbeatWire>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AgentsDeleteParams {
pub agent_id: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct AgentsDeleteResponse {
pub removed: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn agents_list_filter_default_serialises_compact() {
let f = AgentsListFilter::default();
let v = serde_json::to_value(&f).unwrap();
assert_eq!(v, serde_json::json!({ "active_only": false }));
}
#[test]
fn agent_summary_round_trip() {
let s = AgentSummary {
id: "ana".into(),
active: true,
model_provider: "minimax".into(),
bindings_count: 2,
};
let v = serde_json::to_value(&s).unwrap();
let back: AgentSummary = serde_json::from_value(v).unwrap();
assert_eq!(s, back);
}
#[test]
fn agent_detail_skips_none_language() {
let d = AgentDetail {
id: "ana".into(),
model: ModelRef {
provider: "minimax".into(),
model: "MiniMax-M2.5".into(),
},
active: true,
allowed_tools: vec!["*".into()],
inbound_bindings: vec![],
system_prompt: "hi".into(),
language: None,
workspace: String::new(),
extra_docs: vec![],
heartbeat: None,
};
let v = serde_json::to_value(&d).unwrap();
let obj = v.as_object().unwrap();
assert!(!obj.contains_key("language"));
assert!(!obj.contains_key("heartbeat"));
}
#[test]
fn agent_detail_heartbeat_round_trip() {
let d = AgentDetail {
id: "ana".into(),
model: ModelRef {
provider: "minimax".into(),
model: "MiniMax-M2.5".into(),
},
active: true,
allowed_tools: vec!["*".into()],
inbound_bindings: vec![],
system_prompt: String::new(),
language: None,
workspace: String::new(),
extra_docs: vec![],
heartbeat: Some(HeartbeatWire {
enabled: true,
interval: "30m".into(),
}),
};
let v = serde_json::to_value(&d).unwrap();
let back: AgentDetail = serde_json::from_value(v).unwrap();
assert_eq!(d, back);
}
#[test]
fn agent_upsert_input_omits_none_fields_on_serialise() {
let i = AgentUpsertInput {
id: "ana".into(),
model: ModelRef {
provider: "minimax".into(),
model: "MiniMax-M2.5".into(),
},
allowed_tools: None,
inbound_bindings: None,
system_prompt: None,
language: None,
active: None,
transcripts_dir: None,
workspace: None,
extra_docs: None,
heartbeat: None,
};
let v = serde_json::to_value(&i).unwrap();
let obj = v.as_object().unwrap();
assert_eq!(obj.len(), 2);
assert!(obj.contains_key("id"));
assert!(obj.contains_key("model"));
}
}