use crate::{Error, ErrorKind};
use serde_json::{json, Value};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HistoryRole {
User,
Assistant,
System,
}
#[derive(Clone, Debug, PartialEq)]
pub enum HistoryItem {
Message {
id: Option<String>,
role: HistoryRole,
text: String,
},
FunctionCall {
id: Option<String>,
call_id: String,
name: String,
arguments: String,
},
FunctionResult {
id: Option<String>,
call_id: String,
output: String,
},
}
impl HistoryItem {
pub fn id(&self) -> Option<&str> {
match self {
Self::Message { id, .. }
| Self::FunctionCall { id, .. }
| Self::FunctionResult { id, .. } => id.as_deref(),
}
}
}
pub(super) fn settings(item: &HistoryItem) -> Result<Value, Error> {
let bad = || Error::new(ErrorKind::Input, "invalid history item");
if item
.id()
.is_some_and(|id| id.is_empty() || id.chars().count() > 32)
{
return Err(bad());
}
match item {
HistoryItem::Message { text, .. } if text.chars().count() > 4000 => return Err(bad()),
HistoryItem::FunctionCall { call_id, name, .. }
if call_id.is_empty() || name.is_empty() =>
{
return Err(bad())
}
HistoryItem::FunctionResult { call_id, .. } if call_id.is_empty() => return Err(bad()),
_ => {}
}
let mut value = match item {
HistoryItem::Message { role, text, .. } => {
json!({"type":"message","role":match role {HistoryRole::User=>"user",HistoryRole::Assistant=>"assistant",HistoryRole::System=>"system"},"text":text})
}
HistoryItem::FunctionCall {
call_id,
name,
arguments,
..
} => json!({"type":"function_call","call_id":call_id,"name":name,"arguments":arguments}),
HistoryItem::FunctionResult {
call_id, output, ..
} => json!({"type":"function_call_output","call_id":call_id,"output":output}),
};
if let Some(id) = item.id() {
value["id"] = json!(id)
}
Ok(value)
}
pub(super) fn listed(value: &Value) -> Result<HistoryItem, Error> {
let bad = || Error::new(ErrorKind::Stream, "invalid history item");
let field = |key: &str| value[key].as_str().map(str::to_owned).ok_or_else(bad);
let id = Some(field("id")?);
let item = match value["type"].as_str() {
Some("message") => HistoryItem::Message {
id,
role: match value["role"].as_str() {
Some("user") => HistoryRole::User,
Some("assistant") => HistoryRole::Assistant,
Some("system") => HistoryRole::System,
_ => return Err(bad()),
},
text: field("text")?,
},
Some("function_call") => HistoryItem::FunctionCall {
id,
call_id: field("call_id")?,
name: field("name")?,
arguments: field("arguments")?,
},
Some("function_call_output") => HistoryItem::FunctionResult {
id,
call_id: field("call_id")?,
output: field("output")?,
},
_ => return Err(bad()),
};
if item.id().is_none_or(str::is_empty) {
return Err(bad());
}
match &item {
HistoryItem::FunctionCall { call_id, name, .. }
if call_id.is_empty() || name.is_empty() =>
{
return Err(bad())
}
HistoryItem::FunctionResult { call_id, .. } if call_id.is_empty() => return Err(bad()),
_ => {}
}
Ok(item)
}