use serde::{Deserialize, Serialize};
use crate::records::JsonValue;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolSpec {
pub name: String,
pub description: String,
pub parameters: JsonValue,
}
impl ToolSpec {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
parameters: JsonValue,
) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
}
}
pub fn payload_value(&self) -> JsonValue {
object([
("name", JsonValue::String(self.name.clone())),
("description", JsonValue::String(self.description.clone())),
("parameters", self.parameters.clone()),
])
}
pub fn from_payload(value: &JsonValue) -> Option<Self> {
let fields = value.as_object()?;
let name = fields.get("name").and_then(JsonValue::as_str)?;
Some(Self {
name: name.to_owned(),
description: fields
.get("description")
.and_then(JsonValue::as_str)
.unwrap_or("")
.to_owned(),
parameters: fields
.get("parameters")
.cloned()
.unwrap_or_else(empty_object),
})
}
pub fn from_payload_array(value: Option<&JsonValue>) -> Vec<Self> {
value
.and_then(JsonValue::as_array)
.map(|entries| entries.iter().filter_map(Self::from_payload).collect())
.unwrap_or_default()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: JsonValue,
}
impl ToolCall {
pub fn new(name: impl Into<String>, arguments: JsonValue) -> Self {
Self::with_id(new_call_id(), name, arguments)
}
pub fn with_id(id: impl Into<String>, name: impl Into<String>, arguments: JsonValue) -> Self {
Self {
id: id.into(),
name: name.into(),
arguments,
}
}
pub fn payload_value(&self) -> JsonValue {
object([
("id", JsonValue::String(self.id.clone())),
("name", JsonValue::String(self.name.clone())),
("arguments", self.arguments.clone()),
])
}
pub fn from_payload(value: &JsonValue) -> Option<Self> {
let fields = value.as_object()?;
let name = fields.get("name").and_then(JsonValue::as_str)?;
let arguments = fields
.get("arguments")
.cloned()
.unwrap_or_else(empty_object);
if !matches!(arguments, JsonValue::Object(_)) {
return None;
}
let id = fields
.get("id")
.and_then(JsonValue::as_str)
.filter(|id| !id.is_empty());
Some(match id {
Some(id) => Self::with_id(id, name, arguments),
None => Self::new(name, arguments),
})
}
}
fn object<const N: usize>(pairs: [(&str, JsonValue); N]) -> JsonValue {
JsonValue::Object(pairs.into_iter().map(|(k, v)| (k.to_owned(), v)).collect())
}
fn empty_object() -> JsonValue {
JsonValue::Object(std::collections::BTreeMap::new())
}
fn new_call_id() -> String {
use std::hash::{BuildHasher, Hasher};
let entropy = std::collections::hash_map::RandomState::new()
.build_hasher()
.finish();
format!("call_{}", hex::encode(entropy.to_le_bytes()))
}