use serde::Deserialize;
use serde::Serialize;
use crate::json::JsonValue;
use crate::shared::ApprovalId;
use crate::shared::FileData;
use crate::shared::MediaType;
use crate::shared::ProviderOptions;
use crate::shared::ToolCallId;
use crate::shared::ToolName;
use super::content::CustomKind;
pub type Prompt = Vec<PromptMessage>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "role", rename_all = "lowercase")]
#[non_exhaustive]
pub enum PromptMessage {
System {
content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
User {
content: Vec<UserPromptPart>,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
Assistant {
content: Vec<AssistantPromptPart>,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
Tool {
content: Vec<ToolPromptPart>,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
}
impl PromptMessage {
#[must_use]
pub fn system(content: impl Into<String>) -> Self {
Self::System {
content: content.into(),
provider_options: None,
}
}
#[must_use]
pub fn user_text(text: impl Into<String>) -> Self {
Self::User {
content: vec![UserPromptPart::Text(TextPart::new(text))],
provider_options: None,
}
}
#[must_use]
pub fn user(content: Vec<UserPromptPart>) -> Self {
Self::User {
content,
provider_options: None,
}
}
#[must_use]
pub fn assistant_text(text: impl Into<String>) -> Self {
Self::Assistant {
content: vec![AssistantPromptPart::Text(TextPart::new(text))],
provider_options: None,
}
}
#[must_use]
pub fn assistant(content: Vec<AssistantPromptPart>) -> Self {
Self::Assistant {
content,
provider_options: None,
}
}
#[must_use]
pub fn tool(content: Vec<ToolPromptPart>) -> Self {
Self::Tool {
content,
provider_options: None,
}
}
#[must_use]
pub fn role(&self) -> &'static str {
match self {
Self::System { .. } => "system",
Self::User { .. } => "user",
Self::Assistant { .. } => "assistant",
Self::Tool { .. } => "tool",
}
}
#[must_use]
pub fn provider_options(&self) -> Option<&ProviderOptions> {
match self {
Self::System {
provider_options, ..
}
| Self::User {
provider_options, ..
}
| Self::Assistant {
provider_options, ..
}
| Self::Tool {
provider_options, ..
} => provider_options.as_ref(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
#[non_exhaustive]
pub enum UserPromptPart {
Text(TextPart),
File(FilePart),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
#[non_exhaustive]
pub enum AssistantPromptPart {
Text(TextPart),
File(FilePart),
Reasoning(ReasoningPart),
ReasoningFile(ReasoningFilePart),
Custom(CustomPart),
ToolCall(ToolCallPart),
ToolResult(ToolResultPart),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
#[non_exhaustive]
pub enum ToolPromptPart {
ToolResult(ToolResultPart),
ToolApprovalResponse(ToolApprovalResponsePart),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TextPart {
pub text: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_options: Option<ProviderOptions>,
}
impl TextPart {
#[must_use]
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
provider_options: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReasoningPart {
pub text: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_options: Option<ProviderOptions>,
}
impl ReasoningPart {
#[must_use]
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
provider_options: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReasoningFilePart {
pub data: FileData,
pub media_type: MediaType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_options: Option<ProviderOptions>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CustomPart {
pub kind: CustomKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_options: Option<ProviderOptions>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FilePart {
pub data: FileData,
pub media_type: MediaType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub filename: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_options: Option<ProviderOptions>,
}
impl FilePart {
#[must_use]
pub fn new(data: impl Into<FileData>, media_type: impl Into<MediaType>) -> Self {
Self {
data: data.into(),
media_type: media_type.into(),
filename: None,
provider_options: None,
}
}
#[must_use]
pub fn with_filename(mut self, filename: impl Into<String>) -> Self {
self.filename = Some(filename.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolCallPart {
pub tool_call_id: ToolCallId,
pub tool_name: ToolName,
pub input: JsonValue,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub provider_executed: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_options: Option<ProviderOptions>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolResultPart {
pub tool_call_id: ToolCallId,
pub tool_name: ToolName,
pub output: ToolResultOutput,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_options: Option<ProviderOptions>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolApprovalResponsePart {
pub approval_id: ApprovalId,
pub approved: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_options: Option<ProviderOptions>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
#[non_exhaustive]
pub enum ToolResultOutput {
Text {
value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
Json {
value: JsonValue,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
ExecutionDenied {
#[serde(default, skip_serializing_if = "Option::is_none")]
reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
ErrorText {
value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
ErrorJson {
value: JsonValue,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
Content {
value: Vec<ToolResultContentPart>,
},
}
impl ToolResultOutput {
#[must_use]
pub fn text(value: impl Into<String>) -> Self {
Self::Text {
value: value.into(),
provider_options: None,
}
}
#[must_use]
pub fn json(value: JsonValue) -> Self {
Self::Json {
value,
provider_options: None,
}
}
#[must_use]
pub fn error_text(value: impl Into<String>) -> Self {
Self::ErrorText {
value: value.into(),
provider_options: None,
}
}
#[must_use]
pub fn error_json(value: JsonValue) -> Self {
Self::ErrorJson {
value,
provider_options: None,
}
}
#[must_use]
pub fn execution_denied(reason: Option<String>) -> Self {
Self::ExecutionDenied {
reason,
provider_options: None,
}
}
#[must_use]
pub fn is_error(&self) -> bool {
matches!(self, Self::ErrorText { .. } | Self::ErrorJson { .. })
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
#[non_exhaustive]
pub enum ToolResultContentPart {
Text {
text: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
File {
data: FileData,
media_type: MediaType,
#[serde(default, skip_serializing_if = "Option::is_none")]
filename: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
Custom {
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_options: Option<ProviderOptions>,
},
}