Skip to main content

weavatrix_rust/operations/catalog/
mod.rs

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