1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use ts_rs::TS;
8
9#[derive(Debug, Clone, Serialize, Deserialize, TS)]
11#[ts(export)]
12pub struct FunctionTool {
13 pub name: String,
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub description: Option<String>,
18 pub input_schema: Value,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub strict: Option<bool>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub provider_options: Option<HashMap<String, Value>>,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub input_examples: Option<Vec<Value>>,
32}
33
34impl FunctionTool {
35 pub fn new(name: impl Into<String>, input_schema: Value) -> Self {
39 FunctionTool {
40 name: name.into(),
41 description: None,
42 input_schema,
43 strict: None,
44 provider_options: None,
45 input_examples: None,
46 }
47 }
48
49 pub fn with_description(mut self, description: impl Into<String>) -> Self {
51 self.description = Some(description.into());
52 self
53 }
54
55 pub fn with_strict(mut self, strict: bool) -> Self {
57 self.strict = Some(strict);
58 self
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, TS)]
69#[ts(export)]
70pub struct ProviderTool {
71 pub id: String,
73 pub name: String,
75 pub args: Value,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, TS)]
84#[serde(tag = "type", rename_all = "snake_case")]
85#[ts(export)]
86pub enum Tool {
87 Function(FunctionTool),
89 Provider(ProviderTool),
91}
92
93impl From<FunctionTool> for Tool {
94 fn from(ft: FunctionTool) -> Self {
95 Tool::Function(ft)
96 }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize, TS)]
101#[ts(export)]
102pub struct ToolCall {
103 pub tool_call_id: String,
105 pub tool_name: String,
107 pub input: Value,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub provider_executed: Option<bool>,
112 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub dynamic: Option<bool>,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub thought_signature: Option<String>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, TS)]
125#[ts(export)]
126pub struct ToolResult {
127 pub tool_call_id: String,
129 pub result: Value,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub is_error: Option<bool>,
134 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub preliminary: Option<bool>,
137}
138
139#[derive(Debug, Clone, Default, PartialEq, TS)]
146#[ts(
147 export,
148 type = "\"auto\" | \"none\" | \"required\" | { type: \"tool\"; toolName: string }"
149)]
150pub enum ToolChoice {
151 #[default]
153 Auto,
154 None,
156 Required,
158 Tool { tool_name: String },
160}
161
162impl Serialize for ToolChoice {
163 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
164 where
165 S: serde::Serializer,
166 {
167 use serde::ser::SerializeStruct;
168
169 match self {
170 ToolChoice::Auto => serializer.serialize_str("auto"),
171 ToolChoice::None => serializer.serialize_str("none"),
172 ToolChoice::Required => serializer.serialize_str("required"),
173 ToolChoice::Tool { tool_name } => {
174 let mut s = serializer.serialize_struct("ToolChoice", 2)?;
175 s.serialize_field("type", "tool")?;
176 s.serialize_field("toolName", tool_name)?;
177 s.end()
178 }
179 }
180 }
181}
182
183impl<'de> Deserialize<'de> for ToolChoice {
184 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
185 where
186 D: serde::Deserializer<'de>,
187 {
188 use serde::de::Error;
189
190 #[derive(Deserialize)]
191 #[serde(untagged)]
192 enum Repr {
193 Str(String),
194 Tool {
195 #[serde(rename = "type")]
196 typ: String,
197 #[serde(rename = "toolName")]
198 tool_name: Option<String>,
199 },
200 }
201
202 match Repr::deserialize(deserializer)? {
203 Repr::Str(s) => match s.as_str() {
204 "auto" => Ok(ToolChoice::Auto),
205 "none" => Ok(ToolChoice::None),
206 "required" => Ok(ToolChoice::Required),
207 other => Err(D::Error::custom(format!(
208 "unknown toolChoice '{other}' (expected auto/none/required or {{type:\"tool\",toolName}})"
209 ))),
210 },
211 Repr::Tool { typ, tool_name } => {
212 if typ != "tool" {
213 return Err(D::Error::custom(format!(
214 "unknown toolChoice type '{typ}' (expected \"tool\")"
215 )));
216 }
217 let tool_name = tool_name.ok_or_else(|| {
218 D::Error::custom("toolChoice {type:\"tool\"} requires a toolName field")
219 })?;
220 Ok(ToolChoice::Tool { tool_name })
221 }
222 }
223 }
224}