use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use ts_rs::TS;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct FunctionTool {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub input_schema: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_options: Option<HashMap<String, Value>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input_examples: Option<Vec<Value>>,
}
impl FunctionTool {
pub fn new(name: impl Into<String>, input_schema: Value) -> Self {
FunctionTool {
name: name.into(),
description: None,
input_schema,
strict: None,
provider_options: None,
input_examples: None,
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_strict(mut self, strict: bool) -> Self {
self.strict = Some(strict);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ProviderTool {
pub id: String,
pub name: String,
pub args: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(export)]
pub enum Tool {
Function(FunctionTool),
Provider(ProviderTool),
}
impl From<FunctionTool> for Tool {
fn from(ft: FunctionTool) -> Self {
Tool::Function(ft)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ToolCall {
pub tool_call_id: String,
pub tool_name: String,
pub input: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_executed: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dynamic: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ToolResult {
pub tool_call_id: String,
pub result: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub preliminary: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq, TS)]
#[ts(
export,
type = "\"auto\" | \"none\" | \"required\" | { type: \"tool\"; toolName: string }"
)]
pub enum ToolChoice {
#[default]
Auto,
None,
Required,
Tool { tool_name: String },
}
impl Serialize for ToolChoice {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
match self {
ToolChoice::Auto => serializer.serialize_str("auto"),
ToolChoice::None => serializer.serialize_str("none"),
ToolChoice::Required => serializer.serialize_str("required"),
ToolChoice::Tool { tool_name } => {
let mut s = serializer.serialize_struct("ToolChoice", 2)?;
s.serialize_field("type", "tool")?;
s.serialize_field("toolName", tool_name)?;
s.end()
}
}
}
}
impl<'de> Deserialize<'de> for ToolChoice {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
#[derive(Deserialize)]
#[serde(untagged)]
enum Repr {
Str(String),
Tool {
#[serde(rename = "type")]
typ: String,
#[serde(rename = "toolName")]
tool_name: Option<String>,
},
}
match Repr::deserialize(deserializer)? {
Repr::Str(s) => match s.as_str() {
"auto" => Ok(ToolChoice::Auto),
"none" => Ok(ToolChoice::None),
"required" => Ok(ToolChoice::Required),
other => Err(D::Error::custom(format!(
"unknown toolChoice '{other}' (expected auto/none/required or {{type:\"tool\",toolName}})"
))),
},
Repr::Tool { typ, tool_name } => {
if typ != "tool" {
return Err(D::Error::custom(format!(
"unknown toolChoice type '{typ}' (expected \"tool\")"
)));
}
let tool_name = tool_name.ok_or_else(|| {
D::Error::custom("toolChoice {type:\"tool\"} requires a toolName field")
})?;
Ok(ToolChoice::Tool { tool_name })
}
}
}
}