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 docs: &'static str,
11}
12
13impl Entry {
14 const fn new(name: &'static str, docs: &'static str) -> Self {
15 Self { name, docs }
16 }
17}
18
19const RUST_CODE_CREATE: &str = r#"Invoke as RustCodeCreate with arguments matching exactly this JSON Schema: {"type":"object","properties":{"library":{"type":"string"}},"required":["library"],"additionalProperties":false}. Example: {"library":"package-name"}. library must be canonical lowercase-kebab. The backend-selected Profile supplies the authority; authority is never a model argument. Create is only for a brand-new authority-qualified library family. It persists a canonical 0.0.0 working source through KTO, emits editable Documentation.md, Cargo.toml, and ordered src/lib.rs Tool Messages, and returns Created unpublished version <24-lowercase-hex-id>. If the family already exists, use RustCodeOpen."#;
20const RUST_CODE_DOCS: &str = r#"Invoke as RustCodeDocs with arguments matching exactly this JSON Schema: {"type":"object","properties":{"library":{"type":"string"},"version":{"type":"string"}},"required":["library"],"additionalProperties":false}. Examples: {"library":"package-name"}, {"library":"package-name","version":"1.2.3"}, or {"library":"package-name","version":"0123456789abcdef01234567"}. library is canonical lowercase-kebab. version, when supplied, is either a canonical stable SemVer selecting an exact published version or a canonical 24-lowercase-hex unpublished ID. The backend-selected Profile supplies authority. Without version, Docs always returns the greatest published SemVer and never an unpublished head. Success returns a Published/Unpublished version heading, a blank line, and exact Documentation.md. It does not establish an editable snapshot or mutate source."#;
21const RUST_CODE_OPEN: &str = r#"Invoke as RustCodeOpen with arguments matching exactly this JSON Schema: {"type":"object","properties":{"library":{"type":"string"},"version":{"type":"string"}},"required":["library"],"additionalProperties":false}. Examples: {"library":"package-name"}, {"library":"package-name","version":"1.2.3"}, or {"library":"package-name","version":"0123456789abcdef01234567"}. library is canonical lowercase-kebab. version, when supplied, is either a canonical stable SemVer or a canonical 24-lowercase-hex unpublished ID. The backend-selected Profile supplies authority. Without version, Open selects the current family head: the most recently written unpublished branch after the last publication, otherwise the greatest published SemVer. It emits editable Documentation.md, Cargo.toml, and ordered src/lib.rs Tool Messages and returns the exact opened selector. Published source is public; unpublished source is access-controlled."#;
22const RUST_CODE_OVERWRITE: &str = r#"Invoke as RustCodeOverwrite with arguments matching exactly this JSON Schema: {"type":"object","properties":{"box_id":{"type":"integer","minimum":1},"contents":{"type":"string"}},"required":["box_id","contents"],"additionalProperties":false}. Example: {"box_id":42,"contents":"complete replacement contents"}. box_id identifies one original editable Tool Message in the active Rust source. contents completely replaces that segment. Every successful call persists the complete resulting source through KTO. The first overwrite of published source creates and returns a new unpublished 24-hex ID; later overwrites retain that ID. Stale revisions fail. Multiple unpublished branches may coexist. If the affected complete file lacks a trailing LF, K1 appends exactly one; an existing LF, including CRLF, is unchanged."#;
23const RUST_CODE_CHECK: &str = r#"Invoke as RustCodeCheck with arguments matching exactly this JSON Schema: {"type":"object","properties":{"box_id":{"type":"integer","minimum":1}},"required":["box_id"],"additionalProperties":false}. Example: {"box_id":42}. box_id identifies any original editable Tool Message in the active Rust source. Every call runs a fresh complete check against the exact active source. It changes only disposable check state, returns Check passed. or complete failure text, and does not mutate or publish source."#;
24const RUST_CODE_PUBLISH: &str = r#"Invoke as RustCodePublish with arguments matching exactly this JSON Schema: {"type":"object","properties":{"box_id":{"type":"integer","minimum":1},"version":{"type":"string"}},"required":["box_id","version"],"additionalProperties":false}. Example: {"box_id":42,"version":"1.2.3"}. box_id identifies the active Rust source and version is a canonical stable SemVer unused in that authority/library family. The SemVer is declared only at publication. K1 rewrites only [package].version in an exact publication candidate, checks that candidate, preserves it as an Object, reauthorizes immediately before the KTO publication effect, and leaves the unpublished source unchanged. Success returns Published version <semver>. An already-used SemVer is rejected even for identical source."#;
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 canonical 24-character lowercase hexadecimal, name canonical lowercase-kebab, and version canonical stable SemVer. With authority, it returns exact Documentation.md and does not open, mutate, 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}. language is required for an absent package and must match an existing package when supplied. It establishes the active Open and emits Documentation.md plus editable code spans."#;
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}. box_id is the canonical positive decimal ID of an original editable output from the active WebCodeOpen. contents completely replaces that output; continue using the original active-Open 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}. Identity must match the active WebCodeOpen. Every call runs a fresh complete check and retains same-source success or failure evidence."#;
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}. Identity must match the active WebCodeOpen. It reuses valid unchanged success evidence, suppresses publication after unchanged failure, or runs a fresh check; then saves the source Object before one immutable public release. It does not adopt, restart, or deploy."#;
31
32static CATALOG: [Entry; 24] = [
33 Entry::new(
34 "KtoolDocs",
35 r#"Invoke as KtoolDocs {"name":"<exact Ktool name>"}. Arguments contain exactly one nonempty string. Matching is exact and case-sensitive. Success returns compact JSON with name, latest_version, docs, deprecated, and replacement. The operation 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 are invalid. It is stateless."#,
40 ),
41 Entry::new(
42 "KmapCreateNode",
43 r#"Invoke as KmapCreateNode {"title":string,"navigation_hint":string,"narrative":string,"connections":[string,...]}. Connections are exact 24-lowercase-hex node IDs. Using active Kmap authorization, it creates one node, adds Navigation connections, marks it loaded, and returns its ID."#,
44 ),
45 Entry::new(
46 "KmapOpenNode",
47 r#"Invoke as KmapOpenNode {"node_id":string,"budget":number}. node_id is exact 24-lowercase-hex and budget finite nonnegative. It opens Full mode at temperature 1, tracks first-pull provenance, and returns only components not already returned this session."#,
48 ),
49 Entry::new(
50 "KmapUpdateNode",
51 r#"Invoke as KmapUpdateNode {"node_id":string,"title":string,"navigation_hint":string,"narrative":string,"connections":[string,...]}. IDs are exact 24-lowercase-hex. It replaces text fields, additively upserts Navigation connections, and marks the node loaded."#,
52 ),
53 Entry::new(
54 "KmapPenalizeNodes",
55 r#"Invoke as KmapPenalizeNodes {"node_ids":[string,...]}. IDs are exact 24-lowercase-hex. It deduplicates and applies one noncritical negative measurement to each newly eligible loaded node with first-pull provenance; those nodes are excluded from later KmapConnectNodes."#,
56 ),
57 Entry::new(
58 "KmapConnectNodes",
59 r#"Invoke as KmapConnectNodes {}. Using active Kmap authorization, it adds every missing directed Automated connection among loaded unpenalized session nodes. Existing connections remain and self-connections are omitted."#,
60 ),
61 Entry::new(
62 "SendMessage",
63 r#"Invoke as SendMessage {"message":string}. The single message must be nonempty. It durably appends one visible Agent Message to the authenticated current conversation and leaves the provider turn open. It has no cross-conversation or network destination."#,
64 ),
65 Entry::new(
66 "WebSearch",
67 r#"Invoke as WebSearch {"query":string,"model":"codex/<model>","reasoning_effort":string?}. model requires the exact case-sensitive codex/ prefix; effort defaults to medium. It starts one isolated asynchronous search with a 60-minute deadline. Metadata does not authorize or prove provider availability."#,
68 ),
69 Entry::new(
70 "SetLaunchNode",
71 r#"Invoke as SetLaunchNode {"target":string,"node_id":string}. target is exact and node_id a visible 24-lowercase-hex AccessId. With current-user authority, it creates or updates that target's launch-node reference."#,
72 ),
73 Entry::new(
74 "ListContacts",
75 r#"Invoke as ListContacts {}. Returns the complete caller-owned Known Contacts union with user_id, username, person_id, full_name, and nullable kmap_launch_node."#,
76 ),
77 Entry::new(
78 "ListGroups",
79 r#"Invoke as ListGroups {}. Returns visible ordinary human memberships excluding Known Contacts and filtered groups, with group_id, name, revision, caller_role, and kmap_launch_node, and no counts."#,
80 ),
81 Entry::new(
82 "GetGroup",
83 r#"Invoke as GetGroup {"group_id":string}. User-or-active-model visibility suffices. Returns group_id, name, revision, kmap_launch_node, users, and models; user rows contain user_id, username, person_id, full_name, role, kmap_launch_node; model rows contain model_id and nullable name. No messages or counts."#,
84 ),
85 Entry::new("RustCodeCreate", RUST_CODE_CREATE),
86 Entry::new("RustCodeDocs", RUST_CODE_DOCS),
87 Entry::new("RustCodeOpen", RUST_CODE_OPEN),
88 Entry::new("RustCodeOverwrite", RUST_CODE_OVERWRITE),
89 Entry::new("RustCodeCheck", RUST_CODE_CHECK),
90 Entry::new("RustCodePublish", RUST_CODE_PUBLISH),
91 Entry::new("WebCodeDocs", WEB_CODE_DOCS),
92 Entry::new("WebCodeOpen", WEB_CODE_OPEN),
93 Entry::new("WebCodeOverwrite", WEB_CODE_OVERWRITE),
94 Entry::new("WebCodeCheck", WEB_CODE_CHECK),
95 Entry::new("WebCodePublish", WEB_CODE_PUBLISH),
96];
97
98#[derive(Deserialize)]
99#[serde(deny_unknown_fields)]
100struct Arguments {
101 name: String,
102}
103
104#[derive(Serialize)]
105struct Response<'a> {
106 name: &'a str,
107 latest_version: &'static str,
108 docs: &'a str,
109 deprecated: bool,
110 replacement: Option<&'a str>,
111}
112
113fn find_entry(name: &str) -> Option<&'static Entry> {
114 CATALOG.iter().find(|entry| entry.name == name)
115}
116
117fn render(entry: &Entry) -> Result<String, String> {
118 serde_json::to_string(&Response {
119 name: entry.name,
120 latest_version: "1.0.0",
121 docs: entry.docs,
122 deprecated: false,
123 replacement: None,
124 })
125 .map_err(|error| format!("KtoolDocs response serialization failed: {error}"))
126}
127
128pub fn is_known_ktool(name: &str) -> bool {
129 find_entry(name).is_some()
130}
131
132pub fn ktool_docs(arguments: &str) -> Result<String, String> {
133 let arguments: Arguments =
134 serde_json::from_str(arguments).map_err(|_| INVALID_ARGUMENTS.to_owned())?;
135 if arguments.name.is_empty() {
136 return Err(INVALID_ARGUMENTS.into());
137 }
138 render(find_entry(&arguments.name).ok_or_else(|| UNKNOWN_KTOOL.to_owned())?)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use serde_json::Value;
145
146 fn query(name: &str) -> String {
147 serde_json::json!({"name": name}).to_string()
148 }
149
150 #[test]
151 fn catalog_is_exact_and_remains_v1() {
152 assert_eq!(CATALOG.len(), 24);
153 for entry in &CATALOG {
154 assert!(is_known_ktool(entry.name));
155 let value: Value =
156 serde_json::from_str(&ktool_docs(&query(entry.name)).unwrap()).unwrap();
157 assert_eq!(value.as_object().unwrap().len(), 5);
158 assert_eq!(value["latest_version"], "1.0.0");
159 assert_eq!(value["deprecated"], false);
160 assert!(value["replacement"].is_null());
161 }
162 }
163
164 #[test]
165 fn rust_contract_freezes_persistent_selector_semantics() {
166 assert!(RUST_CODE_CREATE.contains(r#""required":["library"]"#));
167 assert!(!RUST_CODE_CREATE.contains(r#""required":["library","version"]"#));
168 for docs in [RUST_CODE_DOCS, RUST_CODE_OPEN] {
169 assert!(docs.contains("when supplied"));
170 assert!(docs.contains("24-lowercase-hex"));
171 }
172 assert!(RUST_CODE_DOCS.contains("greatest published SemVer"));
173 assert!(RUST_CODE_OPEN.contains("current family head"));
174 assert!(RUST_CODE_OVERWRITE.contains("Every successful call persists"));
175 assert!(RUST_CODE_OVERWRITE.contains("Multiple unpublished branches"));
176 assert!(RUST_CODE_PUBLISH.contains(r#""required":["box_id","version"]"#));
177 assert!(RUST_CODE_PUBLISH.contains("declared only at publication"));
178 assert!(RUST_CODE_PUBLISH.contains("leaves the unpublished source unchanged"));
179 for docs in [RUST_CODE_CREATE, RUST_CODE_DOCS, RUST_CODE_OPEN] {
180 assert!(!docs.contains(r#""authority":{"type""#));
181 }
182 }
183
184 #[test]
185 fn malformed_and_unknown_arguments_fail_closed() {
186 for input in [
187 "",
188 "null",
189 "[]",
190 "{}",
191 r#"{"name":""}"#,
192 r#"{"name":1}"#,
193 r#"{"name":"KtoolDocs","extra":false}"#,
194 r#"{"name":"KtoolDocs","name":"CurrentTime"}"#,
195 ] {
196 assert_eq!(ktool_docs(input), Err(INVALID_ARGUMENTS.into()));
197 }
198 assert_eq!(ktool_docs(&query("Unknown")), Err(UNKNOWN_KTOOL.into()));
199 assert!(!is_known_ktool(" WebSearch"));
200 }
201}