use std::num::NonZeroUsize;
use serde_json::{Value, json};
use super::tools::ToolResult;
pub struct HeldWords<'a> {
draft: &'a str,
words: &'a str,
}
impl std::fmt::Debug for HeldWords<'_> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.debug_struct("HeldWords").finish_non_exhaustive()
}
}
#[derive(Debug, thiserror::Error)]
#[error("held_result_invalid")]
pub struct HeldResultInvalid;
impl<'a> HeldWords<'a> {
pub fn new(
draft: &'a str,
words: &'a str,
identifier_bytes: NonZeroUsize,
words_bytes: NonZeroUsize,
) -> Result<Self, HeldResultInvalid> {
if draft.is_empty()
|| draft.len() > identifier_bytes.get()
|| draft.chars().any(char::is_control)
|| words.is_empty()
|| words.len() > words_bytes.get()
{
return Err(HeldResultInvalid);
}
Ok(Self { draft, words })
}
#[must_use]
pub fn tool_result(&self) -> ToolResult {
ToolResult::ok(json!({"draft":self.draft, "words":self.words}))
}
}
#[must_use]
pub fn text(result: &ToolResult) -> Option<String> {
if result.is_error {
return None;
}
let object = result.structured.as_object()?;
if object.len() != 2 {
return None;
}
let draft = object.get("draft")?.as_str()?;
let words = object.get("words")?.as_str()?;
if draft.is_empty() || draft.chars().any(char::is_control) || words.is_empty() {
return None;
}
Some(format!("held by draft {draft}: {words}"))
}
pub fn from_wire(
answer: &Value,
identifier_bytes: NonZeroUsize,
words_bytes: NonZeroUsize,
) -> Result<Option<ToolResult>, HeldResultInvalid> {
let paired = answer.as_object().is_some_and(|object| {
object.len() == 2 && object.contains_key("draft") && object.contains_key("words")
});
if answer.get("state").and_then(Value::as_str) != Some("held") && !paired {
return Ok(None);
}
if answer.get("refusal").is_some()
|| answer.get("error").is_some()
|| answer
.get("isError")
.is_some_and(|value| value.as_bool() != Some(false))
|| answer
.get("is_error")
.is_some_and(|value| value.as_bool() != Some(false))
{
return Err(HeldResultInvalid);
}
let draft = answer
.get("draft")
.and_then(Value::as_str)
.ok_or(HeldResultInvalid)?;
let words = answer
.get("words")
.and_then(Value::as_str)
.ok_or(HeldResultInvalid)?;
Ok(Some(
HeldWords::new(draft, words, identifier_bytes, words_bytes)?.tool_result(),
))
}
impl std::fmt::Debug for ToolResult {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ToolResult")
.field("is_error", &self.is_error)
.finish_non_exhaustive()
}
}
#[cfg(test)]
#[path = "held_tests.rs"]
mod tests;