Skip to main content

starweaver_model/message/
tool.rs

1//! Canonical tool call and tool return parts.
2
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Value};
5
6use super::Metadata;
7
8/// Metadata key binding an approval-required return to the exact arguments that were reviewed.
9pub const TOOL_RETURN_APPROVAL_ARGUMENTS_METADATA_KEY: &str = "starweaver.approval_tool_arguments";
10
11/// Function-style tool call.
12#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
13pub struct ToolCallPart {
14    /// Provider or runtime call identifier.
15    pub id: String,
16    /// Tool name.
17    pub name: String,
18    /// Tool-call argument state.
19    #[serde(default)]
20    pub arguments: ToolArguments,
21}
22
23/// Tool-call argument state preserved across provider mapping, retries, and replay.
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub enum ToolArguments {
26    /// Parsed JSON arguments ready for execution.
27    Parsed(Value),
28    /// Raw JSON string preserved for delayed parsing or provider wire replay.
29    RawJsonString(String),
30    /// Invalid JSON with original provider text and parser message.
31    Invalid {
32        /// Original provider argument text.
33        raw: String,
34        /// Parser error text.
35        error: String,
36    },
37}
38
39impl Default for ToolArguments {
40    fn default() -> Self {
41        Self::Parsed(Value::Null)
42    }
43}
44
45impl ToolArguments {
46    const INVALID_KIND: &'static str = "starweaver_invalid_tool_arguments";
47
48    /// Build parsed JSON arguments.
49    #[must_use]
50    pub const fn parsed(value: Value) -> Self {
51        Self::Parsed(value)
52    }
53
54    /// Build raw JSON string arguments.
55    #[must_use]
56    pub fn raw_json_string(raw: impl Into<String>) -> Self {
57        Self::RawJsonString(raw.into())
58    }
59
60    /// Build invalid JSON arguments.
61    #[must_use]
62    pub fn invalid(raw: impl Into<String>, error: impl Into<String>) -> Self {
63        Self::Invalid {
64            raw: raw.into(),
65            error: error.into(),
66        }
67    }
68
69    /// Parse provider argument payload while preserving invalid input.
70    #[must_use]
71    pub fn from_provider_value(value: &Value) -> Self {
72        match value {
73            Value::String(raw) => match serde_json::from_str::<Value>(raw) {
74                Ok(parsed) => Self::Parsed(parsed),
75                Err(error) => Self::Invalid {
76                    raw: raw.clone(),
77                    error: error.to_string(),
78                },
79            },
80            other => Self::Parsed(other.clone()),
81        }
82    }
83
84    /// JSON value used for local tool execution and output functions.
85    #[must_use]
86    pub fn execution_value(&self) -> Value {
87        match self {
88            Self::Parsed(value) => value.clone(),
89            Self::RawJsonString(raw) => {
90                serde_json::from_str(raw).unwrap_or_else(|_| Value::String(raw.clone()))
91            }
92            Self::Invalid { raw, .. } => Value::String(raw.clone()),
93        }
94    }
95
96    /// JSON string used in provider wire requests.
97    #[must_use]
98    pub fn wire_json_string(&self) -> String {
99        match self {
100            Self::Parsed(value) => value.to_string(),
101            Self::RawJsonString(raw) | Self::Invalid { raw, .. } => raw.clone(),
102        }
103    }
104
105    /// Replay/display value carrying state evidence.
106    #[must_use]
107    pub fn replay_value(&self) -> Value {
108        match self {
109            Self::Parsed(value) => value.clone(),
110            Self::RawJsonString(raw) => Value::String(raw.clone()),
111            Self::Invalid { raw, error } => {
112                let mut object = Map::new();
113                object.insert(
114                    "kind".to_string(),
115                    Value::String(Self::INVALID_KIND.to_string()),
116                );
117                object.insert("raw".to_string(), Value::String(raw.clone()));
118                object.insert("error".to_string(), Value::String(error.clone()));
119                Value::Object(object)
120            }
121        }
122    }
123
124    /// Return the invalid parser error when present.
125    #[must_use]
126    pub const fn invalid_error(&self) -> Option<&str> {
127        match self {
128            Self::Invalid { error, .. } => Some(error.as_str()),
129            Self::Parsed(_) | Self::RawJsonString(_) => None,
130        }
131    }
132}
133
134impl From<Value> for ToolArguments {
135    fn from(value: Value) -> Self {
136        Self::Parsed(value)
137    }
138}
139
140impl From<Map<String, Value>> for ToolArguments {
141    fn from(value: Map<String, Value>) -> Self {
142        Self::Parsed(Value::Object(value))
143    }
144}
145
146impl From<&str> for ToolArguments {
147    fn from(value: &str) -> Self {
148        Self::RawJsonString(value.to_string())
149    }
150}
151
152impl From<String> for ToolArguments {
153    fn from(value: String) -> Self {
154        Self::RawJsonString(value)
155    }
156}
157
158impl PartialEq<Value> for ToolArguments {
159    fn eq(&self, other: &Value) -> bool {
160        &self.execution_value() == other
161    }
162}
163
164impl PartialEq<ToolArguments> for Value {
165    fn eq(&self, other: &ToolArguments) -> bool {
166        self == &other.execution_value()
167    }
168}
169
170impl Serialize for ToolArguments {
171    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
172    where
173        S: serde::Serializer,
174    {
175        self.replay_value().serialize(serializer)
176    }
177}
178
179impl<'de> Deserialize<'de> for ToolArguments {
180    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
181    where
182        D: serde::Deserializer<'de>,
183    {
184        let value = Value::deserialize(deserializer)?;
185        if let Value::Object(object) = &value
186            && object
187                .get("kind")
188                .and_then(Value::as_str)
189                .is_some_and(|kind| kind == Self::INVALID_KIND)
190        {
191            return Ok(Self::Invalid {
192                raw: object
193                    .get("raw")
194                    .and_then(Value::as_str)
195                    .unwrap_or_default()
196                    .to_string(),
197                error: object
198                    .get("error")
199                    .and_then(Value::as_str)
200                    .unwrap_or_default()
201                    .to_string(),
202            });
203        }
204        Ok(Self::Parsed(value))
205    }
206}
207
208/// Tool return content.
209#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
210pub struct ToolReturnPart {
211    /// Related call identifier.
212    pub tool_call_id: String,
213    /// Tool name.
214    pub name: String,
215    /// Tool content sent back to the model.
216    pub content: Value,
217    /// Tool result status.
218    #[serde(default)]
219    pub is_error: bool,
220    /// Tool return metadata for approval, deferral, and runtime orchestration.
221    #[serde(default, skip_serializing_if = "Map::is_empty")]
222    pub metadata: Metadata,
223    /// Application-facing return value when it differs from model-visible content.
224    #[serde(default, skip_serializing_if = "Option::is_none")]
225    pub app_value: Option<Value>,
226    /// User-facing content for UI display and host rendering.
227    #[serde(default, skip_serializing_if = "Option::is_none")]
228    pub user_content: Option<Value>,
229    /// Private host metadata kept separate from provider request mapping.
230    #[serde(default, skip_serializing_if = "Map::is_empty")]
231    pub private_metadata: Metadata,
232}
233
234impl ToolReturnPart {
235    /// Build a tool return part with model-visible content.
236    #[must_use]
237    pub fn new(tool_call_id: impl Into<String>, name: impl Into<String>, content: Value) -> Self {
238        Self {
239            tool_call_id: tool_call_id.into(),
240            name: name.into(),
241            content,
242            is_error: false,
243            metadata: Metadata::default(),
244            app_value: None,
245            user_content: None,
246            private_metadata: Metadata::default(),
247        }
248    }
249
250    /// Mark this tool return as an error.
251    #[must_use]
252    pub const fn with_error(mut self, is_error: bool) -> Self {
253        self.is_error = is_error;
254        self
255    }
256
257    /// Attach public runtime metadata.
258    #[must_use]
259    pub fn with_metadata(mut self, metadata: Metadata) -> Self {
260        self.metadata = metadata;
261        self
262    }
263
264    /// Attach application-facing return value.
265    #[must_use]
266    pub fn with_app_value(mut self, app_value: Value) -> Self {
267        self.app_value = Some(app_value);
268        self
269    }
270
271    /// Attach user-facing content.
272    #[must_use]
273    pub fn with_user_content(mut self, user_content: Value) -> Self {
274        self.user_content = Some(user_content);
275        self
276    }
277
278    /// Attach private host metadata.
279    #[must_use]
280    pub fn with_private_metadata(mut self, private_metadata: Metadata) -> Self {
281        self.private_metadata = private_metadata;
282        self
283    }
284}