use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum UserInput {
Text(String),
Parts(Vec<ContentPart>),
}
impl From<String> for UserInput {
fn from(value: String) -> Self {
Self::Text(value)
}
}
impl From<&str> for UserInput {
fn from(value: &str) -> Self {
Self::Text(value.to_string())
}
}
impl From<Vec<ContentPart>> for UserInput {
fn from(value: Vec<ContentPart>) -> Self {
Self::Parts(value)
}
}
impl From<String> for ContentPart {
fn from(value: String) -> Self {
Self::Text(TextPart { text: value })
}
}
impl From<&str> for ContentPart {
fn from(value: &str) -> Self {
Self::Text(TextPart {
text: value.to_string(),
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
#[non_exhaustive]
pub enum ContentPart {
Text(TextPart),
Think(ThinkPart),
#[serde(rename = "image_url")]
ImageUrl(ImageUrlPart),
#[serde(rename = "audio_url")]
AudioUrl(AudioUrlPart),
#[serde(rename = "video_url")]
VideoUrl(VideoUrlPart),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TextPart {
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ThinkPart {
pub think: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub encrypted: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ImageUrlPart {
pub image_url: MediaUrl,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AudioUrlPart {
pub audio_url: MediaUrl,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VideoUrlPart {
pub video_url: MediaUrl,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MediaUrl {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DisplayBlock {
#[serde(rename = "type")]
pub block_type: DisplayBlockType,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub old_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub new_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub is_summary: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Vec<TodoDisplayItem>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DisplayBlockType {
Brief,
Diff,
Todo,
Shell,
#[serde(rename = "unknown")]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TodoDisplayItem {
pub title: String,
pub status: TodoStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TodoStatus {
Pending,
InProgress,
Done,
}
impl DisplayBlock {
pub fn brief(text: impl Into<String>) -> Self {
Self {
block_type: DisplayBlockType::Brief,
text: Some(text.into()),
path: None,
old_text: None,
new_text: None,
is_summary: None,
items: None,
language: None,
command: None,
data: None,
}
}
pub fn diff(
path: impl Into<String>,
old_text: impl Into<String>,
new_text: impl Into<String>,
) -> Self {
Self {
block_type: DisplayBlockType::Diff,
text: None,
path: Some(path.into()),
old_text: Some(old_text.into()),
new_text: Some(new_text.into()),
is_summary: None,
items: None,
language: None,
command: None,
data: None,
}
}
#[must_use]
pub const fn todo(items: Vec<TodoDisplayItem>) -> Self {
Self {
block_type: DisplayBlockType::Todo,
text: None,
path: None,
old_text: None,
new_text: None,
is_summary: None,
items: Some(items),
language: None,
command: None,
data: None,
}
}
pub fn shell(command: impl Into<String>, language: impl Into<String>) -> Self {
Self {
block_type: DisplayBlockType::Shell,
text: None,
path: None,
old_text: None,
new_text: None,
is_summary: None,
items: None,
language: Some(language.into()),
command: Some(command.into()),
data: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolReturnValue {
pub is_error: bool,
pub output: ToolOutput,
pub message: String,
pub display: Vec<DisplayBlock>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extras: Option<serde_json::Value>,
}
impl ToolReturnValue {
pub fn new(message: impl Into<String>) -> Self {
Self {
is_error: false,
output: ToolOutput::Text(String::new()),
message: message.into(),
display: vec![],
extras: None,
}
}
#[must_use]
pub const fn with_error(mut self) -> Self {
self.is_error = true;
self
}
#[must_use]
pub fn with_output(mut self, output: impl Into<ToolOutput>) -> Self {
self.output = output.into();
self
}
#[must_use]
pub fn with_display(mut self, block: DisplayBlock) -> Self {
self.display.push(block);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum ToolOutput {
Text(String),
Parts(Vec<ContentPart>),
}
impl From<String> for ToolOutput {
fn from(value: String) -> Self {
Self::Text(value)
}
}
impl From<&str> for ToolOutput {
fn from(value: &str) -> Self {
Self::Text(value.to_string())
}
}
impl From<Vec<ContentPart>> for ToolOutput {
fn from(value: Vec<ContentPart>) -> Self {
Self::Parts(value)
}
}