use crate::tool_types::ToolCall;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct ProviderOpaqueContent {
pub provider: String,
#[cfg_attr(feature = "openapi", schema(value_type = Object))]
pub content: serde_json::Value,
}
impl ProviderOpaqueContent {
pub fn new(provider: impl Into<String>, content: serde_json::Value) -> Self {
Self {
provider: provider.into(),
content,
}
}
}
pub const TURN_SCOPED_SYSTEM_MARKER: &str = "\u{0}everruns:turn-scoped-system\u{0}";
#[derive(Debug, Clone)]
pub struct Message {
pub native_tool_calls: Vec<crate::native_async::NativeToolCall>,
pub role: MessageRole,
pub content: MessageContent,
pub tool_calls: Option<Vec<ToolCall>>,
pub tool_call_id: Option<String>,
pub phase: Option<crate::execution_phase::ExecutionPhase>,
pub reasoning: Vec<crate::reasoning::ReasoningContentPart>,
pub configuration_update: Option<crate::model::ReasoningEffort>,
}
impl Message {
pub fn text(role: MessageRole, content: impl Into<String>) -> Self {
Self {
native_tool_calls: Vec::new(),
role,
content: MessageContent::Text(content.into()),
tool_calls: None,
tool_call_id: None,
phase: None,
reasoning: Vec::new(),
configuration_update: None,
}
}
pub fn parts(role: MessageRole, parts: Vec<LlmContentPart>) -> Self {
Self {
native_tool_calls: Vec::new(),
role,
content: MessageContent::Parts(parts),
tool_calls: None,
tool_call_id: None,
phase: None,
reasoning: Vec::new(),
configuration_update: None,
}
}
pub fn content_as_text(&self) -> String {
self.content.to_text()
}
pub fn prepend_text_prefix(&mut self, prefix: &str) {
match &mut self.content {
MessageContent::Text(text) => {
*text = format!("{}{}", prefix, text);
}
MessageContent::Parts(parts) => {
for part in parts.iter_mut() {
if let LlmContentPart::Text { text } = part {
*text = format!("{}{}", prefix, text);
return;
}
}
parts.insert(
0,
LlmContentPart::Text {
text: prefix.to_string(),
},
);
}
}
}
pub fn mark_turn_scoped_system(&mut self) {
debug_assert_eq!(self.role, MessageRole::System);
self.prepend_text_prefix(TURN_SCOPED_SYSTEM_MARKER);
}
}
pub fn fold_system_messages(messages: &[Message]) -> Option<String> {
let mut system: Option<String> = None;
for msg in messages {
if msg.role == MessageRole::System {
let text = msg.content.to_text();
system = Some(match system.take() {
Some(existing) if !existing.is_empty() => format!("{existing}\n\n{text}"),
_ => text,
});
}
}
system
}
#[derive(Debug, Clone)]
pub enum MessageContent {
Text(String),
Parts(Vec<LlmContentPart>),
}
impl MessageContent {
pub fn to_text(&self) -> String {
match self {
MessageContent::Text(s) => s.clone(),
MessageContent::Parts(parts) => parts
.iter()
.filter_map(|p| match p {
LlmContentPart::Text { text } => Some(text.clone()),
_ => None,
})
.collect::<Vec<_>>()
.join(""),
}
}
pub fn is_text(&self) -> bool {
matches!(self, MessageContent::Text(_))
}
pub fn is_parts(&self) -> bool {
matches!(self, MessageContent::Parts(_))
}
}
impl From<String> for MessageContent {
fn from(s: String) -> Self {
MessageContent::Text(s)
}
}
impl From<&str> for MessageContent {
fn from(s: &str) -> Self {
MessageContent::Text(s.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum LlmContentPart {
Text { text: String },
Image { url: String },
Audio { url: String },
File {
url: String,
filename: Option<String>,
},
ProviderOpaque(ProviderOpaqueContent),
}
impl LlmContentPart {
pub fn text(text: impl Into<String>) -> Self {
LlmContentPart::Text { text: text.into() }
}
pub fn image(url: impl Into<String>) -> Self {
LlmContentPart::Image { url: url.into() }
}
pub fn audio(url: impl Into<String>) -> Self {
LlmContentPart::Audio { url: url.into() }
}
pub fn file(url: impl Into<String>, filename: Option<String>) -> Self {
LlmContentPart::File {
url: url.into(),
filename,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MessageRole {
System,
User,
Assistant,
Tool,
}
#[deprecated(since = "0.32.0", note = "renamed to `Message`")]
pub type LlmMessage = Message;
#[deprecated(since = "0.32.0", note = "renamed to `MessageContent`")]
pub type LlmMessageContent = MessageContent;
#[deprecated(since = "0.32.0", note = "renamed to `MessageRole`")]
pub type LlmMessageRole = MessageRole;