Skip to main content

agent_types/
tool.rs

1//! Tool-related pure types: Content, ToolMetadata, ToolExposure, ActivationContext.
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6use crate::session::SessionId;
7
8/// Structured content returned by a tool, aligned with the MCP `content`
9/// array shape (no envelope, no orchestration/failure/truncation semantics).
10///
11/// Only `Text` is consumed by the first LLM adapter; `Image` is shape-reserved
12/// and the adapter reports "not supported" rather than silently dropping it.
13/// `Detail` carries machine-readable metadata (e.g. edit line numbers) that
14/// the UI can consume but the LLM never sees.
15#[derive(Clone, Debug, Serialize, Deserialize)]
16#[serde(tag = "type", rename_all = "snake_case")]
17pub enum Content {
18    Text {
19        text: String,
20    },
21    /// Base64-encoded image payload.
22    Image {
23        data: String,
24        mime_type: String,
25    },
26    /// Machine-readable metadata for UI consumption (not sent to the LLM).
27    Detail {
28        data: serde_json::Value,
29    },
30}
31
32impl Content {
33    pub fn text(s: impl Into<String>) -> Self {
34        Content::Text { text: s.into() }
35    }
36
37    pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
38        Content::Image {
39            data: data.into(),
40            mime_type: mime_type.into(),
41        }
42    }
43
44    pub fn detail(data: serde_json::Value) -> Self {
45        Content::Detail { data }
46    }
47}
48
49impl From<Content> for Vec<Content> {
50    fn from(c: Content) -> Self {
51        vec![c]
52    }
53}
54
55/// Join the textual portion of tool output into a single string for display
56/// and session history. Non-text variants (e.g. `Image`, `Detail`) are skipped.
57pub fn content_text(contents: &[Content]) -> String {
58    contents
59        .iter()
60        .filter_map(|c| match c {
61            Content::Text { text } => Some(text.as_str()),
62            Content::Image { .. } | Content::Detail { .. } => None,
63        })
64        .collect::<Vec<_>>()
65        .join("\n")
66}
67
68/// Extract structured detail metadata from tool output. When multiple `Detail`
69/// entries exist, they are merged into a single object. Returns `None` if no
70/// `Detail` entries are present.
71pub fn content_details(contents: &[Content]) -> Option<serde_json::Value> {
72    let details: Vec<&serde_json::Value> = contents
73        .iter()
74        .filter_map(|c| match c {
75            Content::Detail { data } => Some(data),
76            _ => None,
77        })
78        .collect();
79    match details.len() {
80        0 => None,
81        1 => Some(details[0].clone()),
82        _ => {
83            // Merge multiple detail objects into one
84            let mut merged = serde_json::Map::new();
85            for d in details {
86                if let Some(obj) = d.as_object() {
87                    for (k, v) in obj {
88                        merged.insert(k.clone(), v.clone());
89                    }
90                }
91            }
92            Some(serde_json::Value::Object(merged))
93        }
94    }
95}
96
97/// Machine-readable metadata for a registered tool — origin, version, and
98/// runtime requirements in a stable shape consumers can inspect without
99/// parsing the LLM-facing definition JSON.
100#[derive(Clone, Debug, Serialize, Deserialize)]
101pub struct ToolMetadata {
102    /// Tool name (matches `Tool::name`).
103    pub name: String,
104    /// Human-readable description.
105    pub description: String,
106    /// Where this tool comes from: a crate name (e.g. `"phi-tools"`), a
107    /// framework identifier (`"agent-base"`, `"agent-works"`), or
108    /// `"custom"` for user-defined tools.
109    pub origin: String,
110    /// Crate / package version, or `"unknown"` when built outside a crate.
111    pub version: String,
112    /// Optional runtime requirements or capabilities this tool depends on.
113    pub requirements: Vec<String>,
114}
115
116/// Visibility level of a tool to the LLM model.
117///
118/// Controls whether a tool appears in the tool definitions sent to the model
119/// each turn. Defaults to `Direct` for backward compatibility.
120#[derive(Clone, Debug, PartialEq, Eq)]
121pub enum ToolExposure {
122    /// Always visible to the model. This is the default.
123    Direct,
124    /// Conditionally visible — the tool decides via `Tool::should_activate`.
125    Deferred,
126    /// Never visible to the model (internal/framework tools).
127    Hidden,
128}
129
130/// Context passed to `Tool::should_activate` for Deferred tools.
131///
132/// Built once per react-loop iteration; tools inspect it to decide
133/// whether they should be exposed to the model this turn.
134#[derive(Clone, Debug)]
135pub struct ActivationContext {
136    /// Current session ID.
137    pub session_id: SessionId,
138    /// Names of tools already activated (Direct + activated Deferred) this turn.
139    pub current_tools: Vec<String>,
140    /// Workspace / working directory path.
141    pub workspace: PathBuf,
142}