Skip to main content

weavatrix_rust/operations/catalog/
mod.rs

1use blazingly_json::{Map, Value, json};
2use serde::Serialize;
3
4mod definitions;
5mod profile;
6mod schema;
7mod validation;
8
9pub use profile::ToolProfile;
10pub(crate) use validation::{reject_unknown_arguments, require_valid_output_format};
11
12#[derive(Debug, Clone, Serialize)]
13#[serde(rename_all = "camelCase")]
14pub struct ToolDefinition {
15    pub name: &'static str,
16    pub description: &'static str,
17    pub input_schema: Value,
18}
19
20#[must_use]
21pub fn catalog() -> Vec<ToolDefinition> {
22    definitions::SPECS
23        .iter()
24        .filter(|spec| capability_is_compiled(spec.name))
25        .map(|spec| tool(spec.name, spec.description, spec.required))
26        .collect()
27}
28
29#[must_use]
30pub fn catalog_for_profile(profile: ToolProfile) -> Vec<ToolDefinition> {
31    catalog()
32        .into_iter()
33        .filter(|tool| profile.allows(tool.name))
34        .collect()
35}
36
37#[allow(clippy::needless_bool)]
38fn capability_is_compiled(tool: &str) -> bool {
39    if tool == "find_duplicates" {
40        cfg!(feature = "clone")
41    } else if matches!(
42        tool,
43        "change_impact"
44            | "git_history"
45            | "git_read_blob"
46            | "cross_repo_git"
47            | "verified_change"
48            | "graph_diff"
49            | "select_tests"
50            | "perf_attribution"
51    ) {
52        cfg!(feature = "git")
53    } else if tool == "search_code" {
54        cfg!(feature = "search")
55    } else if matches!(tool, "semantic_link" | "seo_link_suggestions") {
56        cfg!(feature = "semantic")
57    } else if tool == "vector_search" {
58        cfg!(feature = "vector")
59    } else if tool == "memory_context" {
60        cfg!(feature = "memory")
61    } else {
62        true
63    }
64}
65
66fn tool(
67    tool_name: &'static str,
68    description: &'static str,
69    required: &[&'static str],
70) -> ToolDefinition {
71    let mut properties = Map::from_iter([
72        (
73            "output_format".to_owned(),
74            json!({
75                "type": "string",
76                "enum": ["text", "json", "structured"],
77                "default": "json",
78                "description": "text returns the concise text block only; json returns \
79                                structured output and mirrors it into text for clients that \
80                                read only content; structured drops that mirror, which is the \
81                                larger copy, and is safe only where the client reads \
82                                structuredContent"
83            }),
84        ),
85        (
86            "expected_repository".to_owned(),
87            json!({
88                "type": "string",
89                "description": "Path or folder name of the repository this call is about; \
90                                the call fails instead of answering when the active \
91                                repository differs. Every answer also carries a \
92                                repository_context block naming its root, revision and \
93                                graph age."
94            }),
95        ),
96    ]);
97    for name in schema::optional_fields(tool_name) {
98        properties.insert((*name).to_owned(), schema::field_schema(tool_name, name));
99    }
100    for name in required {
101        let schema = schema::field_schema(tool_name, name);
102        properties.insert((*name).to_owned(), schema);
103    }
104    let required = required
105        .iter()
106        .map(|value| json!(value))
107        .collect::<Vec<_>>();
108    ToolDefinition {
109        name: tool_name,
110        description,
111        input_schema: json!({
112            "type": "object",
113            "additionalProperties": true,
114            "properties": properties,
115            "required": required
116        }),
117    }
118}