kernel/capabilities/
tools.rs1use serde::{Deserialize, Serialize};
7
8use crate::records::JsonValue;
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct ToolSpec {
14 pub name: String,
15 pub description: String,
16 pub parameters: JsonValue,
17}
18
19impl ToolSpec {
20 pub fn new(
22 name: impl Into<String>,
23 description: impl Into<String>,
24 parameters: JsonValue,
25 ) -> Self {
26 Self {
27 name: name.into(),
28 description: description.into(),
29 parameters,
30 }
31 }
32
33 pub fn payload_value(&self) -> JsonValue {
35 object([
36 ("name", JsonValue::String(self.name.clone())),
37 ("description", JsonValue::String(self.description.clone())),
38 ("parameters", self.parameters.clone()),
39 ])
40 }
41
42 pub fn from_payload(value: &JsonValue) -> Option<Self> {
44 let fields = value.as_object()?;
45 let name = fields.get("name").and_then(JsonValue::as_str)?;
46 Some(Self {
47 name: name.to_owned(),
48 description: fields
49 .get("description")
50 .and_then(JsonValue::as_str)
51 .unwrap_or("")
52 .to_owned(),
53 parameters: fields
54 .get("parameters")
55 .cloned()
56 .unwrap_or_else(empty_object),
57 })
58 }
59
60 pub fn from_payload_array(value: Option<&JsonValue>) -> Vec<Self> {
62 value
63 .and_then(JsonValue::as_array)
64 .map(|entries| entries.iter().filter_map(Self::from_payload).collect())
65 .unwrap_or_default()
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct ToolCall {
73 pub id: String,
74 pub name: String,
75 pub arguments: JsonValue,
76}
77
78impl ToolCall {
79 pub fn new(name: impl Into<String>, arguments: JsonValue) -> Self {
81 Self::with_id(new_call_id(), name, arguments)
82 }
83
84 pub fn with_id(id: impl Into<String>, name: impl Into<String>, arguments: JsonValue) -> Self {
86 Self {
87 id: id.into(),
88 name: name.into(),
89 arguments,
90 }
91 }
92
93 pub fn payload_value(&self) -> JsonValue {
95 object([
96 ("id", JsonValue::String(self.id.clone())),
97 ("name", JsonValue::String(self.name.clone())),
98 ("arguments", self.arguments.clone()),
99 ])
100 }
101
102 pub fn from_payload(value: &JsonValue) -> Option<Self> {
105 let fields = value.as_object()?;
106 let name = fields.get("name").and_then(JsonValue::as_str)?;
107 let arguments = fields
108 .get("arguments")
109 .cloned()
110 .unwrap_or_else(empty_object);
111 if !matches!(arguments, JsonValue::Object(_)) {
112 return None;
113 }
114 let id = fields
115 .get("id")
116 .and_then(JsonValue::as_str)
117 .filter(|id| !id.is_empty());
118 Some(match id {
119 Some(id) => Self::with_id(id, name, arguments),
120 None => Self::new(name, arguments),
121 })
122 }
123}
124
125fn object<const N: usize>(pairs: [(&str, JsonValue); N]) -> JsonValue {
126 JsonValue::Object(pairs.into_iter().map(|(k, v)| (k.to_owned(), v)).collect())
127}
128
129fn empty_object() -> JsonValue {
130 JsonValue::Object(std::collections::BTreeMap::new())
131}
132
133fn new_call_id() -> String {
134 use std::hash::{BuildHasher, Hasher};
135 let entropy = std::collections::hash_map::RandomState::new()
136 .build_hasher()
137 .finish();
138 format!("call_{}", hex::encode(entropy.to_le_bytes()))
139}