1pub mod bash;
2pub mod diff;
3pub mod edit;
4pub mod fs;
5pub mod question;
6pub mod registry;
7pub mod schema;
8pub mod skill;
9pub mod task;
10pub mod todo;
11pub mod web;
12
13use async_trait::async_trait;
14use serde::de::DeserializeOwned;
15use serde::{Deserialize, Serialize};
16use serde_json::{Value, json};
17
18pub use schema::ToolSchema;
19
20#[derive(Debug, Clone)]
21pub struct ToolCall {
22 pub name: String,
23 pub arguments: Value,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ToolResult {
28 pub is_error: bool,
29 pub summary: String,
30 pub content_type: String,
31 pub payload: Value,
32 pub output: String,
33}
34
35impl ToolResult {
36 fn serialization_error(err: serde_json::Error) -> Self {
37 Self::err_text(
38 "serialization_error",
39 format!("failed to serialize output: {err}"),
40 )
41 }
42
43 fn text_result(is_error: bool, summary: impl Into<String>, text: impl Into<String>) -> Self {
44 let output = text.into();
45 Self {
46 is_error,
47 summary: summary.into(),
48 content_type: "text/plain".to_string(),
49 payload: Value::String(output.clone()),
50 output,
51 }
52 }
53
54 fn json_result(
55 is_error: bool,
56 summary: impl Into<String>,
57 content_type: impl Into<String>,
58 payload: Value,
59 fallback: impl Into<String>,
60 ) -> Self {
61 let fallback = fallback.into();
62 let output = serde_json::to_string(&payload).unwrap_or_else(|_| fallback.to_string());
63 Self {
64 is_error,
65 summary: summary.into(),
66 content_type: content_type.into(),
67 payload,
68 output,
69 }
70 }
71
72 pub fn ok_text(summary: impl Into<String>, text: impl Into<String>) -> Self {
73 Self::text_result(false, summary, text)
74 }
75
76 pub fn err_text(summary: impl Into<String>, text: impl Into<String>) -> Self {
77 Self::text_result(true, summary, text)
78 }
79
80 pub fn error(text: impl Into<String>) -> Self {
81 Self::err_text("error", text)
82 }
83
84 pub fn ok_json(summary: impl Into<String>, payload: Value) -> Self {
85 Self::json_result(false, summary, "application/json", payload, "{}")
86 }
87
88 pub fn ok_json_typed(
89 summary: impl Into<String>,
90 content_type: impl Into<String>,
91 payload: Value,
92 ) -> Self {
93 Self::json_result(false, summary, content_type, payload, "{}")
94 }
95
96 pub fn err_json(summary: impl Into<String>, payload: Value) -> Self {
97 Self::json_result(
98 true,
99 summary,
100 "application/json",
101 payload,
102 json!({}).to_string(),
103 )
104 }
105
106 pub fn ok_json_serializable(summary: impl Into<String>, output: &impl Serialize) -> Self {
107 match serde_json::to_value(output) {
108 Ok(value) => Self::ok_json(summary, value),
109 Err(err) => Self::serialization_error(err),
110 }
111 }
112
113 pub fn ok_json_typed_serializable(
114 summary: impl Into<String>,
115 content_type: impl Into<String>,
116 output: &impl Serialize,
117 ) -> Self {
118 match serde_json::to_value(output) {
119 Ok(value) => Self::ok_json_typed(summary, content_type, value),
120 Err(err) => Self::serialization_error(err),
121 }
122 }
123}
124
125pub fn parse_tool_args<T: DeserializeOwned>(args: Value, tool_name: &str) -> Result<T, ToolResult> {
126 serde_json::from_value(args)
127 .map_err(|err| ToolResult::error(format!("invalid {tool_name} args: {err}")))
128}
129
130#[async_trait]
131pub trait Tool: Send + Sync {
132 fn schema(&self) -> ToolSchema;
133 async fn execute(&self, args: Value) -> ToolResult;
134}