Skip to main content

copilot_sdk/
tools.rs

1// Copyright (c) 2026 Elias Bachaalany
2// SPDX-License-Identifier: MIT
3
4//! Tool definition utilities for the Copilot SDK.
5//!
6//! Provides convenience functions for defining tools with automatic
7//! result normalization and error handling.
8
9use crate::types::{Tool, ToolResultObject};
10use serde_json::Value;
11
12/// Normalize any result into a ToolResultObject.
13///
14/// - `None` / null → empty success
15/// - `String` → success with text
16/// - `ToolResultObject` (dict with resultType + textResultForLlm) → pass-through
17/// - Everything else → JSON serialize
18pub fn normalize_result(result: Value) -> ToolResultObject {
19    match result {
20        Value::Null => ToolResultObject {
21            text_result_for_llm: String::new(),
22            binary_results_for_llm: None,
23            result_type: "success".to_string(),
24            error: None,
25            session_log: None,
26            tool_telemetry: None,
27        },
28        Value::String(s) => ToolResultObject {
29            text_result_for_llm: s,
30            binary_results_for_llm: None,
31            result_type: "success".to_string(),
32            error: None,
33            session_log: None,
34            tool_telemetry: None,
35        },
36        Value::Object(ref map)
37            if map.contains_key("resultType") && map.contains_key("textResultForLlm") =>
38        {
39            serde_json::from_value(result).unwrap_or_else(|_| ToolResultObject {
40                text_result_for_llm: "Failed to parse tool result".to_string(),
41                binary_results_for_llm: None,
42                result_type: "failure".to_string(),
43                error: None,
44                session_log: None,
45                tool_telemetry: None,
46            })
47        }
48        other => ToolResultObject {
49            text_result_for_llm: serde_json::to_string(&other).unwrap_or_default(),
50            binary_results_for_llm: None,
51            result_type: "success".to_string(),
52            error: None,
53            session_log: None,
54            tool_telemetry: None,
55        },
56    }
57}
58
59/// Define a tool with metadata for registration on a session.
60///
61/// Returns a `Tool` struct with name, description, and parameters schema.
62/// The handler must be registered separately on the session via
63/// `session.register_tool_with_handler()`.
64///
65/// # Example
66/// ```rust,no_run
67/// use copilot_sdk::tools::define_tool;
68/// use serde_json::json;
69///
70/// let tool = define_tool(
71///     "my_tool",
72///     "A description of my tool",
73///     Some(json!({"type": "object", "properties": {"query": {"type": "string"}}})),
74/// );
75/// // Register on session: session.register_tool_with_handler(tool, Some(handler)).await;
76/// ```
77pub fn define_tool(name: &str, description: &str, parameters_schema: Option<Value>) -> Tool {
78    Tool {
79        name: name.to_string(),
80        description: description.to_string(),
81        parameters_schema: parameters_schema.unwrap_or(serde_json::json!({})),
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use serde_json::json;
89
90    #[test]
91    fn test_normalize_null() {
92        let result = normalize_result(Value::Null);
93        assert_eq!(result.result_type, "success");
94        assert_eq!(result.text_result_for_llm, "");
95    }
96
97    #[test]
98    fn test_normalize_string() {
99        let result = normalize_result(Value::String("hello".to_string()));
100        assert_eq!(result.result_type, "success");
101        assert_eq!(result.text_result_for_llm, "hello");
102    }
103
104    #[test]
105    fn test_normalize_tool_result_passthrough() {
106        let val = json!({
107            "resultType": "success",
108            "textResultForLlm": "tool output"
109        });
110        let result = normalize_result(val);
111        assert_eq!(result.result_type, "success");
112        assert_eq!(result.text_result_for_llm, "tool output");
113    }
114
115    #[test]
116    fn test_normalize_other_value() {
117        let val = json!({"key": "value"});
118        let result = normalize_result(val);
119        assert_eq!(result.result_type, "success");
120        assert!(result.text_result_for_llm.contains("key"));
121    }
122
123    #[test]
124    fn test_define_tool_basic() {
125        let tool = define_tool("test_tool", "A test tool", None);
126        assert_eq!(tool.name, "test_tool");
127        assert_eq!(tool.description, "A test tool");
128    }
129
130    #[test]
131    fn test_define_tool_with_schema() {
132        let schema = json!({"type": "object", "properties": {"q": {"type": "string"}}});
133        let tool = define_tool("search", "Search tool", Some(schema.clone()));
134        assert_eq!(tool.name, "search");
135        assert_eq!(tool.parameters_schema, schema);
136    }
137}