Skip to main content

ag_ui/
tool.rs

1//! Tool definitions and tool calls.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::JsonObject;
7use crate::ids::ToolCallId;
8
9/// A tool the agent may call.
10///
11/// `parameters` is a JSON Schema object describing the call arguments; it is
12/// carried verbatim and never interpreted by this crate.
13#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
14#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
15#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
16pub struct Tool {
17    /// The name the model uses to call this tool.
18    pub name: String,
19    /// What the tool does — the model reads this to decide when to call it.
20    pub description: String,
21    /// JSON Schema for the call arguments.
22    #[serde(default)]
23    pub parameters: Value,
24    /// Arbitrary integration-specific metadata (for example an A2UI schema).
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    #[cfg_attr(
27        feature = "schemars",
28        schemars(with = "Option<std::collections::BTreeMap<String, serde_json::Value>>")
29    )]
30    #[cfg_attr(feature = "utoipa", schema(value_type = Option<Object>))]
31    pub metadata: Option<JsonObject>,
32}
33
34impl Tool {
35    /// Builds a tool definition from a name, description and parameter schema.
36    pub fn new(
37        name: impl Into<String>,
38        description: impl Into<String>,
39        parameters: impl Into<Value>,
40    ) -> Self {
41        Self {
42            name: name.into(),
43            description: description.into(),
44            parameters: parameters.into(),
45            metadata: None,
46        }
47    }
48}
49
50/// Discriminator for the kind of call a [`ToolCall`] represents.
51///
52/// The protocol currently defines exactly one kind. It is modelled as an enum
53/// rather than elided so the literal `"type":"function"` stays on the wire.
54#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
55#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
56#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
57pub enum ToolCallKind {
58    /// A call to a named function with JSON arguments.
59    #[default]
60    #[serde(rename = "function")]
61    Function,
62}
63
64/// The function being invoked, with its arguments.
65#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
66#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
67#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
68pub struct FunctionCall {
69    /// Name of the tool being called.
70    pub name: String,
71    /// Arguments as a JSON *string* — not a parsed object.
72    ///
73    /// Providers stream these incrementally and may emit invalid JSON until the
74    /// call is complete, so the protocol keeps them unparsed.
75    pub arguments: String,
76}
77
78/// One tool invocation requested by the assistant.
79#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
82#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
83pub struct ToolCall {
84    /// Correlates this call with its `TOOL_CALL_*` events and result message.
85    pub id: ToolCallId,
86    /// Always [`ToolCallKind::Function`].
87    #[serde(rename = "type", default)]
88    pub kind: ToolCallKind,
89    /// The function and its arguments.
90    pub function: FunctionCall,
91    /// Opaque provider payload for zero-data-retention reasoning modes.
92    #[serde(default, skip_serializing_if = "Option::is_none")]
93    pub encrypted_value: Option<String>,
94    /// Extra information, open by key. A tool call is not a message, so it
95    /// carries its own rather than folding into the assistant message that
96    /// owns it: several calls can share one parent, and merging them all into
97    /// it would make the result depend on their relative order. Absent or an
98    /// object — a JSON `null` is rejected. See [`crate::metadata`].
99    #[serde(
100        default,
101        deserialize_with = "crate::serde_util::reject_null",
102        skip_serializing_if = "Option::is_none"
103    )]
104    #[cfg_attr(
105        feature = "schemars",
106        schemars(with = "Option<std::collections::BTreeMap<String, serde_json::Value>>")
107    )]
108    #[cfg_attr(feature = "utoipa", schema(value_type = Option<Object>))]
109    pub metadata: Option<JsonObject>,
110}
111
112impl ToolCall {
113    /// Builds a function tool call.
114    pub fn new(
115        id: impl Into<ToolCallId>,
116        name: impl Into<String>,
117        arguments: impl Into<String>,
118    ) -> Self {
119        Self {
120            id: id.into(),
121            kind: ToolCallKind::Function,
122            function: FunctionCall {
123                name: name.into(),
124                arguments: arguments.into(),
125            },
126            encrypted_value: None,
127            metadata: None,
128        }
129    }
130}