1use anyhow::Result;
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10use super::error::ToolError;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ToolSpec {
15 pub name: String,
16 pub description: String,
17 pub input_schema: Value,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub freeform_grammar: Option<ToolFreeformGrammar>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
23pub struct ToolFreeformGrammar {
24 pub syntax: String,
25 pub definition: String,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ToolCall {
31 pub id: String,
32 pub name: String,
33 pub input: Value,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct ToolResult {
39 pub envelope: ToolResultEnvelope,
40 pub should_sleep: bool,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub sleep_duration_ms: Option<u64>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46#[serde(rename_all = "snake_case")]
47pub enum ToolResultStatus {
48 Success,
49 Error,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct ToolResultEnvelope {
54 pub tool_name: String,
55 pub status: ToolResultStatus,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub summary_text: Option<String>,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub result: Option<Value>,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub error: Option<ToolError>,
62}
63
64impl ToolResult {
65 pub fn success(
66 tool_name: impl Into<String>,
67 result: Value,
68 summary_text: Option<String>,
69 ) -> Self {
70 let envelope = ToolResultEnvelope {
71 tool_name: tool_name.into(),
72 status: ToolResultStatus::Success,
73 summary_text,
74 result: Some(result),
75 error: None,
76 };
77 Self {
78 envelope,
79 should_sleep: false,
80 sleep_duration_ms: None,
81 }
82 }
83
84 pub fn sleep(
85 tool_name: impl Into<String>,
86 result: Value,
87 summary_text: Option<String>,
88 sleep_duration_ms: Option<u64>,
89 ) -> Self {
90 let envelope = ToolResultEnvelope {
91 tool_name: tool_name.into(),
92 status: ToolResultStatus::Success,
93 summary_text,
94 result: Some(result),
95 error: None,
96 };
97 Self {
98 envelope,
99 should_sleep: true,
100 sleep_duration_ms,
101 }
102 }
103
104 pub fn error(tool_name: impl Into<String>, error: ToolError) -> Self {
105 let envelope = ToolResultEnvelope {
106 tool_name: tool_name.into(),
107 status: ToolResultStatus::Error,
108 summary_text: Some(error.message.clone()),
109 result: None,
110 error: Some(error.clone()),
111 };
112 Self {
113 envelope,
114 should_sleep: false,
115 sleep_duration_ms: None,
116 }
117 }
118
119 pub fn content_text(&self) -> Result<String> {
120 serde_json::to_string(&self.envelope).map_err(Into::into)
121 }
122
123 pub fn is_error(&self) -> bool {
124 matches!(self.envelope.status, ToolResultStatus::Error)
125 }
126
127 pub fn tool_error(&self) -> Option<&ToolError> {
128 self.envelope.error.as_ref()
129 }
130
131 pub fn summary_text(&self) -> Option<&str> {
132 self.envelope.summary_text.as_deref()
133 }
134}
135
136pub(crate) fn spec(name: &str, description: &str, input_schema: Value) -> ToolSpec {
138 ToolSpec {
139 name: name.to_string(),
140 description: description.to_string(),
141 input_schema,
142 freeform_grammar: None,
143 }
144}
145
146pub(crate) fn typed_spec<T: schemars::JsonSchema + 'static>(
148 name: &str,
149 description: &str,
150) -> Result<ToolSpec> {
151 Ok(spec(
152 name,
153 description,
154 crate::tool::schema::tool_input_schema::<T>()?,
155 ))
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_spec_builder() {
164 let tool_spec = spec(
165 "TestTool",
166 "A test tool",
167 serde_json::json!({"type": "object"}),
168 );
169
170 assert_eq!(tool_spec.name, "TestTool");
171 assert_eq!(tool_spec.description, "A test tool");
172 }
173}