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 #[must_use]
51 pub fn with_description(mut self, description: impl Into<String>) -> Self {
52 self.description = Some(description.into());
53 self
54 }
55
56 #[must_use]
58 pub fn with_strict(mut self, strict: bool) -> Self {
59 self.strict = Some(strict);
60 self
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, TS)]
71#[ts(export)]
72pub struct ProviderTool {
73 pub id: String,
75 pub name: String,
77 pub args: Value,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, TS)]
86#[serde(tag = "type", rename_all = "snake_case")]
87#[ts(export)]
88pub enum Tool {
89 Function(FunctionTool),
91 Provider(ProviderTool),
93}
94
95impl From<FunctionTool> for Tool {
96 fn from(ft: FunctionTool) -> Self {
97 Tool::Function(ft)
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize, TS)]
103#[ts(export)]
104pub struct ToolCall {
105 pub tool_call_id: String,
107 pub tool_name: String,
109 pub input: Value,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub provider_executed: Option<bool>,
114 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub dynamic: Option<bool>,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub thought_signature: Option<String>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, TS)]
127#[ts(export)]
128pub struct ToolResult {
129 pub tool_call_id: String,
131 pub result: Value,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub is_error: Option<bool>,
136 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub preliminary: Option<bool>,
139}
140
141#[derive(Debug, Clone, Default, PartialEq, TS)]
148#[ts(
149 export,
150 type = "\"auto\" | \"none\" | \"required\" | { type: \"tool\"; toolName: string }"
151)]
152pub enum ToolChoice {
153 #[default]
155 Auto,
156 None,
158 Required,
160 Tool { tool_name: String },
162}
163
164impl Serialize for ToolChoice {
165 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
166 where
167 S: serde::Serializer,
168 {
169 use serde::ser::SerializeStruct;
170
171 match self {
172 ToolChoice::Auto => serializer.serialize_str("auto"),
173 ToolChoice::None => serializer.serialize_str("none"),
174 ToolChoice::Required => serializer.serialize_str("required"),
175 ToolChoice::Tool { tool_name } => {
176 let mut s = serializer.serialize_struct("ToolChoice", 2)?;
177 s.serialize_field("type", "tool")?;
178 s.serialize_field("toolName", tool_name)?;
179 s.end()
180 }
181 }
182 }
183}
184
185impl<'de> Deserialize<'de> for ToolChoice {
186 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
187 where
188 D: serde::Deserializer<'de>,
189 {
190 use serde::de::Error;
191
192 #[derive(Deserialize)]
193 #[serde(untagged)]
194 enum Repr {
195 Str(String),
196 Tool {
197 #[serde(rename = "type")]
198 typ: String,
199 #[serde(rename = "toolName")]
200 tool_name: Option<String>,
201 },
202 }
203
204 match Repr::deserialize(deserializer)? {
205 Repr::Str(s) => match s.as_str() {
206 "auto" => Ok(ToolChoice::Auto),
207 "none" => Ok(ToolChoice::None),
208 "required" => Ok(ToolChoice::Required),
209 other => Err(D::Error::custom(format!(
210 "unknown toolChoice '{other}' (expected auto/none/required or {{type:\"tool\",toolName}})"
211 ))),
212 },
213 Repr::Tool { typ, tool_name } => {
214 if typ != "tool" {
215 return Err(D::Error::custom(format!(
216 "unknown toolChoice type '{typ}' (expected \"tool\")"
217 )));
218 }
219 let tool_name = tool_name.ok_or_else(|| {
220 D::Error::custom("toolChoice {type:\"tool\"} requires a toolName field")
221 })?;
222 Ok(ToolChoice::Tool { tool_name })
223 }
224 }
225 }
226}