use crate::agent::payload_message::PayloadMessage;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParticipantInfo {
pub name: String,
pub role: String,
pub description: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capabilities: Option<Vec<crate::agent::Capability>>,
}
impl ParticipantInfo {
pub fn new(
name: impl Into<String>,
role: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
name: name.into(),
role: role.into(),
description: description.into(),
capabilities: None,
}
}
pub fn with_capabilities(mut self, capabilities: Vec<crate::agent::Capability>) -> Self {
self.capabilities = Some(capabilities);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TurnInput {
pub user_prompt: String,
#[serde(default)]
pub current_messages: Vec<PayloadMessage>,
#[serde(default)]
pub context: Vec<ContextMessage>,
#[serde(default)]
pub participants: Vec<ParticipantInfo>,
#[serde(default)]
pub current_participant: String,
}
impl TurnInput {
pub fn new(user_prompt: impl Into<String>) -> Self {
Self {
user_prompt: user_prompt.into(),
current_messages: Vec::new(),
context: Vec::new(),
participants: Vec::new(),
current_participant: String::new(),
}
}
pub fn with_context(user_prompt: impl Into<String>, context: Vec<ContextMessage>) -> Self {
Self {
user_prompt: user_prompt.into(),
current_messages: Vec::new(),
context,
participants: Vec::new(),
current_participant: String::new(),
}
}
pub fn with_dialogue_context(
user_prompt: impl Into<String>,
context: Vec<ContextMessage>,
participants: Vec<ParticipantInfo>,
current_participant: impl Into<String>,
) -> Self {
Self {
user_prompt: user_prompt.into(),
current_messages: Vec::new(),
context,
participants,
current_participant: current_participant.into(),
}
}
pub fn with_messages_and_context(
current_messages: Vec<PayloadMessage>,
context: Vec<ContextMessage>,
participants: Vec<ParticipantInfo>,
current_participant: impl Into<String>,
) -> Self {
Self {
user_prompt: String::new(),
current_messages,
context,
participants,
current_participant: current_participant.into(),
}
}
pub fn to_messages(&self) -> Vec<PayloadMessage> {
use crate::agent::dialogue::Speaker;
let mut messages = Vec::new();
for ctx in &self.context {
let speaker = if ctx.speaker_role == "System" {
Speaker::System
} else {
Speaker::agent(&ctx.speaker_name, &ctx.speaker_role)
};
messages.push(PayloadMessage::new(speaker, ctx.content.clone()));
}
if !self.current_messages.is_empty() {
messages.extend(self.current_messages.clone());
} else if !self.user_prompt.is_empty() {
messages.push(PayloadMessage::system(self.user_prompt.clone()));
}
messages
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextMessage {
pub speaker_name: String,
pub speaker_role: String,
pub content: String,
#[serde(default)]
pub turn: usize,
#[serde(default)]
pub timestamp: u64,
}
impl ContextMessage {
pub fn new(
speaker_name: impl Into<String>,
speaker_role: impl Into<String>,
content: impl Into<String>,
) -> Self {
Self {
speaker_name: speaker_name.into(),
speaker_role: speaker_role.into(),
content: content.into(),
turn: 0,
timestamp: 0,
}
}
pub fn with_metadata(
speaker_name: impl Into<String>,
speaker_role: impl Into<String>,
content: impl Into<String>,
turn: usize,
timestamp: u64,
) -> Self {
Self {
speaker_name: speaker_name.into(),
speaker_role: speaker_role.into(),
content: content.into(),
turn,
timestamp,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agent::dialogue::Speaker;
#[test]
fn test_turn_input_no_context() {
let input = TurnInput::new("Test prompt");
assert_eq!(input.user_prompt, "Test prompt");
assert!(input.context.is_empty());
let messages = input.to_messages();
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].speaker, Speaker::System);
assert_eq!(messages[0].content, "Test prompt");
}
#[test]
fn test_turn_input_with_context() {
let context = vec![
ContextMessage::new("Alice", "Engineer", "I think we should use Rust"),
ContextMessage::new("Bob", "Designer", "The UI needs to be simple"),
];
let input = TurnInput::with_context("Discuss implementation", context);
assert_eq!(input.user_prompt, "Discuss implementation");
assert_eq!(input.context.len(), 2);
let messages = input.to_messages();
assert_eq!(messages.len(), 3);
assert_eq!(messages[0].speaker, Speaker::agent("Alice", "Engineer"));
assert_eq!(messages[0].content, "I think we should use Rust");
assert_eq!(messages[1].speaker, Speaker::agent("Bob", "Designer"));
assert_eq!(messages[1].content, "The UI needs to be simple");
assert_eq!(messages[2].speaker, Speaker::System);
assert_eq!(messages[2].content, "Discuss implementation");
}
#[test]
fn test_turn_input_with_dialogue_context() {
let participants = vec![
ParticipantInfo::new("Alice", "Engineer", "Expert in system architecture"),
ParticipantInfo::new("Bob", "Designer", "Focuses on user experience"),
];
let context = vec![ContextMessage::with_metadata(
"Alice",
"Engineer",
"I think we should use microservices architecture",
1,
1699000000,
)];
let turn_input = TurnInput::with_dialogue_context(
"Discuss the implementation plan",
context,
participants.clone(),
"Bob",
);
assert_eq!(turn_input.participants.len(), 2);
assert_eq!(turn_input.current_participant, "Bob");
let messages = turn_input.to_messages();
assert_eq!(messages.len(), 2); assert_eq!(messages[0].speaker, Speaker::agent("Alice", "Engineer"));
assert_eq!(
messages[0].content,
"I think we should use microservices architecture"
);
assert_eq!(messages[1].speaker, Speaker::System);
assert_eq!(messages[1].content, "Discuss the implementation plan");
}
#[test]
fn test_context_message_with_metadata() {
let msg = ContextMessage::with_metadata("Alice", "Engineer", "Test message", 5, 1699000000);
assert_eq!(msg.speaker_name, "Alice");
assert_eq!(msg.speaker_role, "Engineer");
assert_eq!(msg.content, "Test message");
assert_eq!(msg.turn, 5);
assert_eq!(msg.timestamp, 1699000000);
}
}