Skip to main content

kcode_k1_ktool_docs/
lib.rs

1#![forbid(unsafe_code)]
2
3use serde::{Deserialize, Serialize};
4
5const INVALID_ARGUMENTS: &str = "invalid KtoolDocs arguments";
6const UNKNOWN_KTOOL: &str = "unknown Ktool";
7
8struct Entry {
9    name: &'static str,
10    version: &'static str,
11    docs: &'static str,
12    replacement: Option<&'static str>,
13}
14
15impl Entry {
16    const fn new(name: &'static str, docs: &'static str) -> Self {
17        Self {
18            name,
19            version: "1.0.0",
20            docs,
21            replacement: None,
22        }
23    }
24}
25
26static CATALOG: [Entry; 13] = [
27    Entry::new(
28        "KtoolDocs",
29        r#"Invoke as KtoolDocs {"name":"<exact Ktool name>"}. Arguments must be exactly one nonempty string field, name; matching is exact and case-sensitive. Success returns compact JSON with exactly name, latest_version, docs, deprecated, and replacement. Malformed arguments return invalid KtoolDocs arguments; an unrecognized exact name returns unknown Ktool. The operation is stateless metadata lookup only and does not list, search, suggest, authorize, register, or prove live availability."#,
30    ),
31    Entry::new(
32        "CurrentTime",
33        r#"Invoke as CurrentTime {}. Returns current system UTC in RFC3339 with exactly three fractional digits and Z. Any nonempty or nonobject arguments return invalid CurrentTime arguments. It is stateless and needs no Kmap authorization."#,
34    ),
35    Entry::new(
36        "KmapCreateNode",
37        r#"Invoke as KmapCreateNode {"title":string,"navigation_hint":string,"narrative":string,"connections":[string,...]}. Each connection must be an exact 24-character lowercase hexadecimal Kmap node ID. Using the active Kmap authorization, it creates one node under the bound profile and policy, adds the supplied Navigation connections, marks the new node loaded, and returns Node successfully created with id <id>. Malformed arguments or IDs return invalid KmapCreateNode arguments; missing authorization and Kmap dependency failures remain errors."#,
38    ),
39    Entry::new(
40        "KmapOpenNode",
41        r#"Invoke as KmapOpenNode {"node_id":string,"budget":number}. node_id must be an exact 24-character lowercase hexadecimal Kmap node ID and budget must be finite and nonnegative. It opens in Full mode at temperature 1, records loaded nodes and first-pull provenance, and returns only components not already returned during the session. If no new components remain, it returns No new Kmap node components. Malformed arguments return invalid KmapOpenNode arguments; missing authorization and Kmap dependency failures remain errors."#,
42    ),
43    Entry::new(
44        "KmapUpdateNode",
45        r#"Invoke as KmapUpdateNode {"node_id":string,"title":string,"navigation_hint":string,"narrative":string,"connections":[string,...]}. All IDs must be exact 24-character lowercase hexadecimal Kmap node IDs. It replaces the node text fields, additively upserts the supplied Navigation connections, marks the updated node loaded, and returns success. Naming another node does not itself load that node. Malformed arguments return invalid KmapUpdateNode arguments; missing authorization and Kmap dependency failures remain errors."#,
46    ),
47    Entry::new(
48        "KmapPenalizeNodes",
49        r#"Invoke as KmapPenalizeNodes {"node_ids":[string,...]}. Every ID must be an exact 24-character lowercase hexadecimal Kmap node ID. It deduplicates the input and applies one noncritical negative measurement to each newly penalized eligible loaded node with first-pull provenance. Penalized nodes are excluded from the session’s later KmapConnectNodes operation. Unknown, unloaded, or already penalized valid IDs have no additional effect. Success returns success. Malformed arguments return invalid KmapPenalizeNodes arguments; missing authorization and Kmap dependency failures remain errors."#,
50    ),
51    Entry::new(
52        "KmapConnectNodes",
53        r#"Invoke as KmapConnectNodes {}. Using the active Kmap authorization, it adds every missing directed Automated connection among loaded, unpenalized session nodes. Existing connections remain unchanged and self-connections are not added. Success returns success. Nonempty or nonobject arguments return invalid KmapConnectNodes arguments; missing authorization and Kmap dependency failures remain errors."#,
54    ),
55    Entry::new(
56        "SendMessage",
57        r#"Invoke as SendMessage {"message":string}. Arguments must contain exactly one nonempty message string. It durably appends one visible Agent Message to the authenticated current conversation between the Tool Call and Tool Result, returns success, and leaves the provider turn open for continued generation. It has no recipient argument, network effect, or cross-conversation capability. Malformed arguments return invalid SendMessage arguments without appending a message; persistence failures remain errors."#,
58    ),
59    Entry::new(
60        "WebSearch",
61        r#"Invoke as WebSearch {"query":string,"model":"codex/<model>","reasoning_effort":string?}. Arguments are strict: query and model are required strings, model must have the exact case-sensitive codex/ prefix, and reasoning_effort is optional, non-null, and defaults to medium. It starts one isolated asynchronous search with an absolute 60-minute deadline and emits no progress. Malformed arguments return invalid WebSearch arguments. This metadata does not authorize a provider call or prove runtime availability."#,
62    ),
63    Entry::new(
64        "SetLaunchNode",
65        r#"Invoke as SetLaunchNode {"target":string,"node_id":string}. Arguments are strict: target is validated as an exact target name and node_id must be a visible AccessId of exactly 24 lowercase hexadecimal characters. With current-user authority, it creates or updates that target’s launch-node reference. It makes no runtime-selection claim."#,
66    ),
67    Entry::new(
68        "ListContacts",
69        r#"Invoke as ListContacts {}. Arguments are strict and must be an empty object. It returns the complete caller-owned exact Known Contacts union. Every row has exactly user_id, username, person_id, full_name, and nullable kmap_launch_node. Authorization and dependency failures remain errors."#,
70    ),
71    Entry::new(
72        "ListGroups",
73        r#"Invoke as ListGroups {}. Arguments are strict and must be an empty object. It returns complete visible ordinary human memberships, excluding Known Contacts and filtered groups. Every row has exactly group_id, name, revision, caller_role, and kmap_launch_node. It returns no counts. Authorization and dependency failures remain errors."#,
74    ),
75    Entry::new(
76        "GetGroup",
77        r#"Invoke as GetGroup {"group_id":string}. Arguments are strict and require group_id. Mere user-or-active-model visibility is sufficient. It returns exactly top-level fields group_id, name, revision, kmap_launch_node, users, and models; each user row has user_id, username, person_id, full_name, role, and kmap_launch_node; each model row has model_id and nullable name. It returns no messages, history, narrative, or counts. Authorization and dependency failures remain errors."#,
78    ),
79];
80
81#[derive(Deserialize)]
82#[serde(deny_unknown_fields)]
83struct Arguments {
84    name: String,
85}
86
87#[derive(Serialize)]
88struct Response<'a> {
89    name: &'a str,
90    latest_version: &'static str,
91    docs: &'a str,
92    deprecated: bool,
93    replacement: Option<&'a str>,
94}
95
96fn find_entry(name: &str) -> Option<&'static Entry> {
97    CATALOG.iter().find(|entry| entry.name == name)
98}
99
100fn render(entry: &Entry) -> Result<String, String> {
101    let response = Response {
102        name: entry.name,
103        latest_version: entry.version,
104        docs: entry.docs,
105        deprecated: entry.replacement.is_some(),
106        replacement: entry.replacement,
107    };
108    serde_json::to_string(&response)
109        .map_err(|error| format!("KtoolDocs response serialization failed: {error}"))
110}
111
112/// Returns whether `name` is an exact, case-sensitive catalog name.
113pub fn is_known_ktool(name: &str) -> bool {
114    find_entry(name).is_some()
115}
116
117/// Looks up the exact Ktool name supplied in a strict JSON argument object.
118pub fn ktool_docs(arguments: &str) -> Result<String, String> {
119    let arguments: Arguments =
120        serde_json::from_str(arguments).map_err(|_| INVALID_ARGUMENTS.to_owned())?;
121    if arguments.name.is_empty() {
122        return Err(INVALID_ARGUMENTS.to_owned());
123    }
124    let entry = find_entry(&arguments.name).ok_or_else(|| UNKNOWN_KTOOL.to_owned())?;
125    render(entry)
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use serde_json::Value;
132
133    fn arguments_for(name: &str) -> String {
134        format!("{{\"name\":{}}}", serde_json::to_string(name).unwrap())
135    }
136
137    #[test]
138    fn every_catalog_entry_has_an_exact_five_field_active_response() {
139        assert_eq!(CATALOG.len(), 13);
140        for entry in &CATALOG {
141            let output = ktool_docs(&arguments_for(entry.name)).unwrap();
142            let value: Value = serde_json::from_str(&output).unwrap();
143            let object = value.as_object().unwrap();
144            assert_eq!(object.len(), 5);
145            assert_eq!(object.get("name").and_then(Value::as_str), Some(entry.name));
146            assert_eq!(
147                object.get("latest_version").and_then(Value::as_str),
148                Some("1.0.0")
149            );
150            assert_eq!(object.get("docs").and_then(Value::as_str), Some(entry.docs));
151            assert_eq!(
152                object.get("deprecated").and_then(Value::as_bool),
153                Some(false)
154            );
155            assert!(object.get("replacement").is_some_and(Value::is_null));
156        }
157    }
158
159    #[test]
160    fn known_lookup_uses_the_same_exact_catalog() {
161        for entry in &CATALOG {
162            assert!(is_known_ktool(entry.name));
163        }
164        for name in ["websearch", " WebSearch", "GetGroup ", "Unknown"] {
165            assert!(!is_known_ktool(name));
166            assert_eq!(
167                ktool_docs(&arguments_for(name)),
168                Err(UNKNOWN_KTOOL.to_owned())
169            );
170        }
171        assert!(!is_known_ktool(""));
172        assert_eq!(
173            ktool_docs(&arguments_for("")),
174            Err(INVALID_ARGUMENTS.to_owned())
175        );
176    }
177
178    #[test]
179    fn new_contracts_state_the_approved_boundaries() {
180        let docs = |name| find_entry(name).unwrap().docs;
181        assert!(docs("WebSearch").contains("exact case-sensitive codex/ prefix"));
182        assert!(docs("WebSearch").contains("defaults to medium"));
183        assert!(docs("WebSearch").contains("one isolated asynchronous search"));
184        assert!(docs("WebSearch").contains("absolute 60-minute deadline"));
185        assert!(docs("SetLaunchNode").contains("visible AccessId"));
186        assert!(docs("SetLaunchNode").contains("current-user authority"));
187        assert!(docs("SetLaunchNode").contains("creates or updates"));
188        assert!(docs("ListContacts").contains("complete caller-owned exact Known Contacts union"));
189        assert!(docs("ListGroups").contains("excluding Known Contacts and filtered groups"));
190        assert!(docs("ListGroups").contains("no counts"));
191        let get_group = docs("GetGroup");
192        assert!(get_group.contains("Mere user-or-active-model visibility"));
193        assert!(get_group.contains(
194            "top-level fields group_id, name, revision, kmap_launch_node, users, and models"
195        ));
196        assert!(
197            get_group
198                .contains("user_id, username, person_id, full_name, role, and kmap_launch_node")
199        );
200        assert!(get_group.contains("model_id and nullable name"));
201        assert!(get_group.contains("no messages, history, narrative, or counts"));
202        assert!(!get_group.contains("caller_role"));
203    }
204
205    #[test]
206    fn malformed_arguments_are_strictly_rejected() {
207        for input in [
208            "",
209            "null",
210            "[]",
211            "{}",
212            r#"{"name":""}"#,
213            r#"{"name":1}"#,
214            r#"{"name":"KtoolDocs","extra":false}"#,
215            r#"{"name":"KtoolDocs","name":"CurrentTime"}"#,
216        ] {
217            assert_eq!(
218                ktool_docs(input),
219                Err(INVALID_ARGUMENTS.to_owned()),
220                "{input:?}"
221            );
222        }
223    }
224
225    #[test]
226    fn response_serialization_escapes_strings() {
227        let entry = Entry {
228            name: "quote\"",
229            version: "1.0.0",
230            docs: "line\n",
231            replacement: None,
232        };
233        let output = render(&entry).unwrap();
234        assert!(output.contains("\\\""));
235        assert!(output.contains("\\n"));
236        assert_eq!(
237            serde_json::from_str::<Value>(&output).unwrap()["docs"],
238            "line\n"
239        );
240    }
241}