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
26const WEB_CODE_DOCS: &str = r#"Invoke as WebCodeDocs with arguments matching exactly this JSON Schema: {"type":"object","properties":{"authority":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"}},"required":["authority","name","version"],"additionalProperties":false}. Example: {"authority":"0123456789abcdef01234567","name":"package-name","version":"1.2.3"}. authority must be a canonical 24-character lowercase hexadecimal string, name must be canonical lowercase-kebab, and version must be a canonical stable version; all are exact and case-sensitive. With authority for the named private or public package version, it returns only that version's exact Documentation.md contents. It does not establish an Open, emit editable source, mutate source, or publish."#;
27const WEB_CODE_OPEN: &str = r#"Invoke as WebCodeOpen with arguments matching exactly this JSON Schema: {"type":"object","properties":{"authority":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"},"language":{"type":"string","enum":["javascript","html","css"]}},"required":["authority","name","version"],"additionalProperties":false}. Existing-package example: {"authority":"0123456789abcdef01234567","name":"package-name","version":"1.2.3"}. Absent-package example: {"authority":"0123456789abcdef01234567","name":"package-name","version":"1.2.3","language":"javascript"}. authority must be a canonical 24-character lowercase hexadecimal string, name must be canonical lowercase-kebab, and version must be a canonical stable version; all are exact and case-sensitive. language is required when the package is absent and, when supplied, must be exactly one of javascript, html, or css; for an existing package, it must match the existing language. It establishes the conversation's active Open for that identity. Documentation.md is editable Tool Message 1; code spans are editable in later Tool Messages and the terminal Tool Result."#;
28const WEB_CODE_OVERWRITE: &str = r#"Invoke as WebCodeOverwrite with arguments matching exactly this JSON Schema: {"type":"object","properties":{"box_id":{"type":"string"},"contents":{"type":"string"}},"required":["box_id","contents"],"additionalProperties":false}. Example: {"box_id":"42","contents":"complete replacement contents"}. box_id must be a canonical positive decimal string identifying an original editable output from the conversation's active WebCodeOpen; copied, discovered, stale, or overwrite-produced IDs are not accepted. contents completely replaces only that output. The box from Tool Message 1 is Documentation.md; boxes from later Tool Messages or the terminal Tool Result are code spans. Continue to address later replacements through the original active-Open box ID."#;
29const WEB_CODE_CHECK: &str = r#"Invoke as WebCodeCheck with arguments matching exactly this JSON Schema: {"type":"object","properties":{"authority":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"}},"required":["authority","name","version"],"additionalProperties":false}. Example: {"authority":"0123456789abcdef01234567","name":"package-name","version":"1.2.3"}. authority must be a canonical 24-character lowercase hexadecimal string, name must be canonical lowercase-kebab, and version must be a canonical stable version; all are exact and case-sensitive and must match the conversation's active WebCodeOpen. Every call runs a fresh complete check of the active source. A successful check retains same-source success evidence for WebCodePublish; a failed check retains the failed-check state for that source."#;
30const WEB_CODE_PUBLISH: &str = r#"Invoke as WebCodePublish with arguments matching exactly this JSON Schema: {"type":"object","properties":{"authority":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"}},"required":["authority","name","version"],"additionalProperties":false}. Example: {"authority":"0123456789abcdef01234567","name":"package-name","version":"1.2.3"}. authority must be a canonical 24-character lowercase hexadecimal string, name must be canonical lowercase-kebab, and version must be a canonical stable version; all are exact and case-sensitive and must match the conversation's active WebCodeOpen. It reuses retained successful check evidence only while the source is unchanged, suppresses publication after an unchanged retained failed check, and otherwise runs a fresh complete precheck. On success it creates the source Object before exactly one public immutable release. It performs no adoption, restart, deployment, or live-behavior change."#;
31
32static CATALOG: [Entry; 24] = [
33    Entry::new(
34        "KtoolDocs",
35        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."#,
36    ),
37    Entry::new(
38        "CurrentTime",
39        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."#,
40    ),
41    Entry::new(
42        "KmapCreateNode",
43        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."#,
44    ),
45    Entry::new(
46        "KmapOpenNode",
47        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."#,
48    ),
49    Entry::new(
50        "KmapUpdateNode",
51        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."#,
52    ),
53    Entry::new(
54        "KmapPenalizeNodes",
55        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."#,
56    ),
57    Entry::new(
58        "KmapConnectNodes",
59        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."#,
60    ),
61    Entry::new(
62        "SendMessage",
63        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."#,
64    ),
65    Entry::new(
66        "WebSearch",
67        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."#,
68    ),
69    Entry::new(
70        "SetLaunchNode",
71        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."#,
72    ),
73    Entry::new(
74        "ListContacts",
75        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."#,
76    ),
77    Entry::new(
78        "ListGroups",
79        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."#,
80    ),
81    Entry::new(
82        "GetGroup",
83        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."#,
84    ),
85    Entry::new(
86        "RustCodeCreate",
87        r#"Invoke as RustCodeCreate with exact string arguments authority, library, and version. It starts an unpublished three-file library and emits editable Documentation.md, Cargo.toml, and Rust source boxes. Edit those boxes with RustCodeOverwrite, then check or publish the library."#,
88    ),
89    Entry::new(
90        "RustCodeDocs",
91        r#"Invoke as RustCodeDocs with exact string arguments authority, library, and version. It returns only the exact Documentation.md contents of the published supported three-file library and does not open editable source."#,
92    ),
93    Entry::new(
94        "RustCodeOpen",
95        r#"Invoke as RustCodeOpen with exact string arguments authority, library, and version. It opens a published supported three-file library and emits editable Documentation.md, Cargo.toml, and ordered Rust source boxes."#,
96    ),
97    Entry::new(
98        "RustCodeOverwrite",
99        r#"Invoke as RustCodeOverwrite with box_id from the current RustCodeCreate or RustCodeOpen output and contents containing the complete replacement for that source box. It changes only that segment; later edits keep using the original box ID."#,
100    ),
101    Entry::new(
102        "RustCodeCheck",
103        r#"Invoke as RustCodeCheck with box_id from any box in the current Rust library. It always runs a fresh complete check and returns only the plain success or failure text."#,
104    ),
105    Entry::new(
106        "RustCodePublish",
107        r#"Invoke as RustCodePublish with box_id from any box in the current Rust library. It may be called without RustCodeCheck; it checks automatically when needed, aborts on failure, and immutably publishes only when authorized."#,
108    ),
109    Entry::new("WebCodeDocs", WEB_CODE_DOCS),
110    Entry::new("WebCodeOpen", WEB_CODE_OPEN),
111    Entry::new("WebCodeOverwrite", WEB_CODE_OVERWRITE),
112    Entry::new("WebCodeCheck", WEB_CODE_CHECK),
113    Entry::new("WebCodePublish", WEB_CODE_PUBLISH),
114];
115
116#[derive(Deserialize)]
117#[serde(deny_unknown_fields)]
118struct Arguments {
119    name: String,
120}
121
122#[derive(Serialize)]
123struct Response<'a> {
124    name: &'a str,
125    latest_version: &'static str,
126    docs: &'a str,
127    deprecated: bool,
128    replacement: Option<&'a str>,
129}
130
131fn find_entry(name: &str) -> Option<&'static Entry> {
132    CATALOG.iter().find(|entry| entry.name == name)
133}
134
135fn render(entry: &Entry) -> Result<String, String> {
136    let response = Response {
137        name: entry.name,
138        latest_version: entry.version,
139        docs: entry.docs,
140        deprecated: entry.replacement.is_some(),
141        replacement: entry.replacement,
142    };
143    serde_json::to_string(&response)
144        .map_err(|error| format!("KtoolDocs response serialization failed: {error}"))
145}
146
147/// Returns whether `name` is an exact, case-sensitive catalog name.
148pub fn is_known_ktool(name: &str) -> bool {
149    find_entry(name).is_some()
150}
151
152/// Looks up the exact Ktool name supplied in a strict JSON argument object.
153pub fn ktool_docs(arguments: &str) -> Result<String, String> {
154    let arguments: Arguments =
155        serde_json::from_str(arguments).map_err(|_| INVALID_ARGUMENTS.to_owned())?;
156    if arguments.name.is_empty() {
157        return Err(INVALID_ARGUMENTS.to_owned());
158    }
159    let entry = find_entry(&arguments.name).ok_or_else(|| UNKNOWN_KTOOL.to_owned())?;
160    render(entry)
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use serde_json::Value;
167
168    fn arguments_for(name: &str) -> String {
169        format!("{{\"name\":{}}}", serde_json::to_string(name).unwrap())
170    }
171
172    #[test]
173    fn every_catalog_entry_has_an_exact_five_field_active_response() {
174        assert_eq!(CATALOG.len(), 24);
175        for entry in &CATALOG {
176            let output = ktool_docs(&arguments_for(entry.name)).unwrap();
177            let value: Value = serde_json::from_str(&output).unwrap();
178            let object = value.as_object().unwrap();
179            assert_eq!(object.len(), 5);
180            assert_eq!(object.get("name").and_then(Value::as_str), Some(entry.name));
181            assert_eq!(
182                object.get("latest_version").and_then(Value::as_str),
183                Some("1.0.0")
184            );
185            assert_eq!(object.get("docs").and_then(Value::as_str), Some(entry.docs));
186            assert_eq!(
187                object.get("deprecated").and_then(Value::as_bool),
188                Some(false)
189            );
190            assert!(object.get("replacement").is_some_and(Value::is_null));
191        }
192    }
193
194    #[test]
195    fn known_lookup_uses_the_same_exact_catalog() {
196        for entry in &CATALOG {
197            assert!(is_known_ktool(entry.name));
198        }
199        for name in ["websearch", " WebSearch", "GetGroup ", "Unknown"] {
200            assert!(!is_known_ktool(name));
201            assert_eq!(
202                ktool_docs(&arguments_for(name)),
203                Err(UNKNOWN_KTOOL.to_owned())
204            );
205        }
206        assert!(!is_known_ktool(""));
207        assert_eq!(
208            ktool_docs(&arguments_for("")),
209            Err(INVALID_ARGUMENTS.to_owned())
210        );
211    }
212
213    #[test]
214    fn new_contracts_state_the_approved_boundaries() {
215        let docs = |name| find_entry(name).unwrap().docs;
216        assert!(docs("WebSearch").contains("exact case-sensitive codex/ prefix"));
217        assert!(docs("WebSearch").contains("defaults to medium"));
218        assert!(docs("WebSearch").contains("one isolated asynchronous search"));
219        assert!(docs("WebSearch").contains("absolute 60-minute deadline"));
220        assert!(docs("SetLaunchNode").contains("visible AccessId"));
221        assert!(docs("SetLaunchNode").contains("current-user authority"));
222        assert!(docs("SetLaunchNode").contains("creates or updates"));
223        assert!(docs("ListContacts").contains("complete caller-owned exact Known Contacts union"));
224        assert!(docs("ListGroups").contains("excluding Known Contacts and filtered groups"));
225        assert!(docs("ListGroups").contains("no counts"));
226        let get_group = docs("GetGroup");
227        assert!(get_group.contains("Mere user-or-active-model visibility"));
228        assert!(get_group.contains(
229            "top-level fields group_id, name, revision, kmap_launch_node, users, and models"
230        ));
231        assert!(
232            get_group
233                .contains("user_id, username, person_id, full_name, role, and kmap_launch_node")
234        );
235        assert!(get_group.contains("model_id and nullable name"));
236        assert!(get_group.contains("no messages, history, narrative, or counts"));
237        assert!(!get_group.contains("caller_role"));
238
239        assert!(docs("RustCodeCreate").contains("authority, library, and version"));
240        assert!(docs("RustCodeCreate").contains("emits editable"));
241        assert!(docs("RustCodeDocs").contains("returns only the exact Documentation.md"));
242        assert!(docs("RustCodeOpen").contains("ordered Rust source boxes"));
243        assert!(docs("RustCodeOverwrite").contains("complete replacement"));
244        assert!(docs("RustCodeOverwrite").contains("original box ID"));
245        assert!(docs("RustCodeCheck").contains("always runs a fresh complete check"));
246        assert!(docs("RustCodePublish").contains("without RustCodeCheck"));
247        assert!(docs("RustCodePublish").contains("checks automatically"));
248        assert!(docs("RustCodePublish").contains("aborts on failure"));
249    }
250
251    #[test]
252    fn web_code_names_are_exact_and_near_misses_are_unknown() {
253        for name in [
254            "WebCodeDocs",
255            "WebCodeOpen",
256            "WebCodeOverwrite",
257            "WebCodeCheck",
258            "WebCodePublish",
259        ] {
260            assert!(is_known_ktool(name));
261            assert!(ktool_docs(&arguments_for(name)).is_ok());
262        }
263        for name in [
264            "webCodeDocs",
265            "WebcodeOpen",
266            "WebCodeOverWrite",
267            "WebCodeChecks",
268            "WebCodePublish ",
269            "WebCodeList",
270            "WebCodeSearch",
271        ] {
272            assert!(!is_known_ktool(name));
273            assert_eq!(
274                ktool_docs(&arguments_for(name)),
275                Err(UNKNOWN_KTOOL.to_owned())
276            );
277        }
278    }
279
280    #[test]
281    fn web_code_contracts_freeze_schemas_examples_and_lifecycle() {
282        let docs = |name| find_entry(name).unwrap().docs;
283        let identity_schema = r#"{"type":"object","properties":{"authority":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"}},"required":["authority","name","version"],"additionalProperties":false}"#;
284        let identity_example =
285            r#"{"authority":"0123456789abcdef01234567","name":"package-name","version":"1.2.3"}"#;
286        let invalid_identity_example =
287            r#"{"authority":"owner","name":"package","version":"1.2.3"}"#;
288        for name in ["WebCodeDocs", "WebCodeCheck", "WebCodePublish"] {
289            let contract = docs(name);
290            assert!(contract.contains(identity_schema));
291            assert!(contract.contains(identity_example));
292            assert!(!contract.contains(invalid_identity_example));
293        }
294        for name in [
295            "WebCodeDocs",
296            "WebCodeOpen",
297            "WebCodeCheck",
298            "WebCodePublish",
299        ] {
300            let contract = docs(name);
301            assert!(contract.contains("canonical 24-character lowercase hexadecimal string"));
302            assert!(contract.contains("name must be canonical lowercase-kebab"));
303            assert!(contract.contains("version must be a canonical stable version"));
304        }
305
306        let open = docs("WebCodeOpen");
307        assert!(
308            open.contains(r#""language":{"type":"string","enum":["javascript","html","css"]}"#)
309        );
310        assert!(open.contains(r#""language":"javascript""#));
311        assert!(!open.contains(r#""language":"rust""#));
312        assert!(open.contains("exactly one of javascript, html, or css"));
313        assert!(open.contains("language is required when the package is absent"));
314        assert!(open.contains("must match the existing language"));
315        assert!(open.contains("conversation's active Open"));
316        assert!(open.contains("Documentation.md is editable Tool Message 1"));
317        assert!(open.contains("terminal Tool Result"));
318
319        let overwrite = docs("WebCodeOverwrite");
320        assert!(overwrite.contains(r#""box_id":{"type":"string"}"#));
321        assert!(overwrite.contains(r#""contents":{"type":"string"}"#));
322        assert!(
323            overwrite.contains(r#"{"box_id":"42","contents":"complete replacement contents"}"#)
324        );
325        assert!(!overwrite.contains("box-id-from-open"));
326        assert!(overwrite.contains("canonical positive decimal string"));
327        assert!(overwrite.contains("original editable output"));
328        assert!(overwrite.contains("Tool Message 1 is Documentation.md"));
329        assert!(overwrite.contains("later Tool Messages or the terminal Tool Result"));
330
331        let check = docs("WebCodeCheck");
332        assert!(check.contains("Every call runs a fresh complete check"));
333        assert!(check.contains("same-source success evidence"));
334        assert!(check.contains("failed-check state"));
335
336        let publish = docs("WebCodePublish");
337        assert!(publish.contains("source is unchanged"));
338        assert!(publish.contains("unchanged retained failed check"));
339        assert!(publish.contains("fresh complete precheck"));
340        assert!(publish.contains("source Object before exactly one public immutable release"));
341        assert!(publish.contains("no adoption, restart, deployment, or live-behavior change"));
342    }
343
344    #[test]
345    fn malformed_arguments_are_strictly_rejected() {
346        for input in [
347            "",
348            "null",
349            "[]",
350            "{}",
351            r#"{"name":""}"#,
352            r#"{"name":1}"#,
353            r#"{"name":"KtoolDocs","extra":false}"#,
354            r#"{"name":"KtoolDocs","name":"CurrentTime"}"#,
355        ] {
356            assert_eq!(
357                ktool_docs(input),
358                Err(INVALID_ARGUMENTS.to_owned()),
359                "{input:?}"
360            );
361        }
362    }
363
364    #[test]
365    fn response_serialization_escapes_strings() {
366        let entry = Entry {
367            name: "quote\"",
368            version: "1.0.0",
369            docs: "line\n",
370            replacement: None,
371        };
372        let output = render(&entry).unwrap();
373        assert!(output.contains("\\\""));
374        assert!(output.contains("\\n"));
375        assert_eq!(
376            serde_json::from_str::<Value>(&output).unwrap()["docs"],
377            "line\n"
378        );
379    }
380}