Skip to main content

isb_server/server/
aliases.rs

1//! Argument spellings a newcomer reaches for, mapped to the canonical ones
2//! before a call is authorized. The schemas and docs show only the canonical
3//! names; the aliases make a guess work instead of failing on an unknown
4//! field.
5
6use serde_json::{Value, json};
7
8use super::Tool;
9
10/// `app` for an `app_*` tool's `name`, and `command` (a string, run as
11/// `sh -c`, or an array) for an exec tool's `argv`. The canonical name wins
12/// when both are given.
13pub fn alias_args(tool: &Tool, mut args: Value) -> Value {
14    let props = tool.input_schema.get("properties");
15    let has = |k: &str| props.is_some_and(|p| p.get(k).is_some());
16    let Some(m) = args.as_object_mut() else {
17        return args;
18    };
19    if tool.name.starts_with("app_") && has("name") && !has("app") && !m.contains_key("name") {
20        if let Some(v) = m.remove("app") {
21            m.insert("name".into(), v);
22        }
23    }
24    if has("argv") && !has("command") && !m.contains_key("argv") {
25        if let Some(v) = m.remove("command") {
26            let v = match v {
27                Value::String(s) => json!(["sh", "-c", s]),
28                other => other,
29            };
30            m.insert("argv".into(), v);
31        }
32    }
33    args
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    fn tool(name: &str) -> Tool {
41        let schema = json!({"type": "object", "properties": {"name": {}, "argv": {}}});
42        Tool::new(name, "x", schema, |a, _| Ok(a))
43    }
44
45    #[test]
46    fn newcomer_names_map_to_the_canonical_ones() {
47        let a = alias_args(&tool("app_exec"), json!({"app": "web", "command": "ls -l"}));
48        assert_eq!(a, json!({"name": "web", "argv": ["sh", "-c", "ls -l"]}));
49        let a = alias_args(
50            &tool("app_exec"),
51            json!({"app": "web", "command": ["ls", "-l"]}),
52        );
53        assert_eq!(a, json!({"name": "web", "argv": ["ls", "-l"]}));
54    }
55
56    #[test]
57    fn the_canonical_name_wins_and_app_means_name_only_on_app_tools() {
58        let a = alias_args(&tool("app_logs"), json!({"app": "x", "name": "web"}));
59        assert_eq!(a, json!({"app": "x", "name": "web"}));
60        let a = alias_args(
61            &tool("instance_exec"),
62            json!({"app": "web", "command": ["id"]}),
63        );
64        assert_eq!(a, json!({"app": "web", "argv": ["id"]}));
65        let a = alias_args(&tool("app_top"), json!("not an object"));
66        assert_eq!(a, json!("not an object"));
67    }
68}