use std::ops::Deref;
use pulldown_cmark::HeadingLevel;
use serde::{Deserialize, Serialize};
pub const DEFAULT_OPTIONS: Options = Options {
inner: pulldown_cmark::Options::empty(),
tool_use: false,
tool_results: false,
system: false,
attrs: false,
heading_level: None,
};
pub const VERBOSE_OPTIONS: Options = Options {
inner: pulldown_cmark::Options::empty(),
tool_use: true,
tool_results: true,
system: true,
attrs: true,
heading_level: None,
};
mod serde_inner {
use super::*;
pub fn serialize<S>(
options: &pulldown_cmark::Options,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
options.bits().serialize(serializer)
}
pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<pulldown_cmark::Options, D::Error>
where
D: serde::Deserializer<'de>,
{
let bits = u32::deserialize(deserializer)?;
Ok(pulldown_cmark::Options::from_bits_truncate(bits))
}
}
#[derive(Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(any(feature = "partial-eq", test), derive(PartialEq))]
#[serde(default)]
pub struct Options {
#[serde(with = "serde_inner")]
pub inner: pulldown_cmark::Options,
pub system: bool,
pub tool_use: bool,
pub tool_results: bool,
pub attrs: bool,
pub heading_level: Option<HeadingLevel>,
}
impl Options {
pub fn verbose() -> Self {
VERBOSE_OPTIONS
}
pub fn with_tool_use(mut self) -> Self {
self.tool_use = true;
self
}
pub fn with_tool_results(mut self) -> Self {
self.tool_results = true;
self
}
pub fn with_system(mut self) -> Self {
self.system = true;
self
}
}
#[cfg(feature = "markdown")]
impl From<pulldown_cmark::Options> for Options {
fn from(inner: pulldown_cmark::Options) -> Self {
Options {
inner,
..Default::default()
}
}
}
#[derive(derive_more::Display)]
#[cfg_attr(any(feature = "partial-eq", test), derive(PartialEq))]
#[display("{text}")]
pub struct Markdown {
text: String,
}
impl From<Markdown> for String {
fn from(markdown: Markdown) -> Self {
markdown.text
}
}
impl AsRef<str> for Markdown {
fn as_ref(&self) -> &str {
self.deref()
}
}
impl std::borrow::Borrow<str> for Markdown {
fn borrow(&self) -> &str {
self.as_ref()
}
}
impl std::ops::Deref for Markdown {
type Target = str;
fn deref(&self) -> &str {
&self.text
}
}
impl<'a, T> From<T> for Markdown
where
T: Iterator<Item = pulldown_cmark::Event<'a>>,
{
fn from(events: T) -> Self {
let mut text = String::new();
let _ = pulldown_cmark_to_cmark::cmark(events, &mut text).unwrap();
Markdown { text }
}
}
#[cfg(any(test, feature = "partial-eq"))]
impl PartialEq<str> for Markdown {
fn eq(&self, other: &str) -> bool {
self.text == other
}
}
pub trait ToMarkdown {
fn markdown(&self) -> Markdown {
self.markdown_events().into()
}
fn markdown_custom(&self, options: Options) -> Markdown {
self.markdown_events_custom(options).into()
}
fn markdown_verbose(&self) -> Markdown {
self.markdown_custom(VERBOSE_OPTIONS)
}
fn write_markdown(
&self,
writer: &mut dyn std::fmt::Write,
) -> std::fmt::Result {
self.write_markdown_custom(writer, DEFAULT_OPTIONS)
}
fn write_markdown_custom(
&self,
writer: &mut dyn std::fmt::Write,
options: Options,
) -> std::fmt::Result {
use pulldown_cmark_to_cmark::cmark;
let events = self.markdown_events_custom(options);
let _ = cmark(events, writer)?;
Ok(())
}
fn markdown_events<'a>(
&'a self,
) -> Box<dyn Iterator<Item = pulldown_cmark::Event<'a>> + 'a> {
self.markdown_events_custom(DEFAULT_OPTIONS)
}
fn markdown_events_custom<'a>(
&'a self,
options: Options,
) -> Box<dyn Iterator<Item = pulldown_cmark::Event<'a>> + 'a>;
}
static_assertions::assert_obj_safe!(ToMarkdown);
impl Default for Options {
fn default() -> Self {
DEFAULT_OPTIONS
}
}
#[cfg(test)]
mod tests {
use crate::prompt::{message::Role, Message};
use super::*;
use std::borrow::Borrow;
#[test]
fn test_options_serde() {
let options = Options::default();
let json = serde_json::to_string(&options).unwrap();
let options2: Options = serde_json::from_str(&json).unwrap();
assert!(options == options2);
}
#[test]
fn test_options_from_pulldown() {
let inner = pulldown_cmark::Options::empty();
let options: Options = inner.into();
assert_eq!(options.inner, inner);
}
#[test]
fn test_options_verbose() {
let options = Options::verbose();
assert!(options.tool_use);
assert!(options.tool_results);
assert!(options.system);
}
#[test]
fn test_markdown() {
let expected = "Hello, **world**!";
let events = pulldown_cmark::Parser::new(&expected);
let markdown: Markdown = events.into();
let actual: &str = markdown.borrow();
assert_eq!(actual, expected);
assert!(&markdown == expected);
let markdown: String = markdown.into();
assert_eq!(markdown, expected);
}
#[test]
fn test_message_markdown() {
let message = Message {
role: Role::User,
content: "Hello, **world**!".into(),
};
assert_eq!(
message.markdown().as_ref(),
"### User\n\nHello, **world**!"
);
}
#[test]
fn test_options_with_system() {
let options = Options::default().with_system();
assert!(options.system);
}
}