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."#;
25const RUST_CODE_BUILD: &str = r#"Invoke as RustCodeBuild with arguments matching exactly this JSON Schema: {"type":"object","properties":{"library":{"type":"string"},"version":{"type":"string"}},"required":["library","version"],"additionalProperties":false}. Example: {"library":"loom","version":"0.1.0"}. library is canonical lowercase-kebab and version is one exact canonical published stable SemVer. The backend-selected Profile supplies authority; authority is never a model argument. Build selects only that published source, uses library as the binary target name, and runs cargo build --release --locked --bin <library>. Success returns the authority-qualified source identity, version, and disposable server code-cache path. It creates no Object or durable binary publication and does not deploy, restart, or execute the binary."#;
26
27const WEB_CODE_CREATE: &str = r#"Invoke as WebCodeCreate with arguments matching exactly this JSON Schema: {"type":"object","properties":{"library":{"type":"string"},"language":{"type":"string","enum":["javascript","html","css"]}},"required":["library","language"],"additionalProperties":false}. Example: {"library":"package-name","language":"javascript"}. library is canonical lowercase-kebab. The backend-selected Profile supplies authority; authority is never a model argument. Create is only for a brand-new family. It persists a versionless KTO draft and emits exactly two complete Named Tool Messages in path order: Documentation.md plus index.js, index.html, or index.css. Each is titled File: <path>. No Code.* file or authored k1-web.json is created. Success returns a stable 24-lowercase-hex unpublished ID."#;
28const WEB_CODE_DOCS: &str = r#"Invoke as WebCodeDocs 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"}. version, when supplied, is canonical stable SemVer or a canonical unpublished 24-lowercase-hex ID. The backend-selected Profile supplies authority. Without version, Docs returns only the greatest published SemVer and never an unpublished workspace. Success emits exact UTF-8 Documentation.md and reports the selected version without establishing editable state."#;
29const WEB_CODE_OPEN: &str = r#"Invoke as WebCodeOpen 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"}. Open never creates. With version it opens that exact published or access-authorized unpublished source. Without version it opens the most recently updated unpublished workspace when its authored bytes are not published; otherwise it opens the greatest published SemVer. It emits every authored file once in lexicographic path order as one complete Named Tool Message titled File: <path>; this revision performs no chunking. UTF-8 bytes are exact. Non-UTF-8 files expose exactly this file is a non-text object and cannot be modified. Success reports the selected version and establishes the active source generation."#;
30const WEB_CODE_OVERWRITE: &str = r#"Invoke as WebCodeOverwrite 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 targets one current UTF-8 file in the active Web source. contents replaces that complete file without normalization. Non-UTF-8 targets fail with exactly this file is a non-text object and cannot be modified. Every successful call submits one complete-tree KTO revision, including byte-identical replacements. Editing published or already-published bytes creates a new unpublished ID; later dirty edits retain it. Retired file boxes are not file targets."#;
31const WEB_CODE_CREATE_FILE: &str = r#"Invoke as WebCodeCreateFile with arguments matching exactly this JSON Schema: {"type":"object","properties":{"box_id":{"type":"integer","minimum":1},"path":{"type":"string"},"contents":{"type":"string"}},"required":["box_id","path","contents"],"additionalProperties":false}. Example: {"box_id":42,"path":"src/component.js","contents":"export const value = 1;\n"}. box_id may be any source box from the active Create/Open generation, including a retired file box, and anchors the workspace rather than naming the destination. path must be a safe slash-relative Web source path and must not already exist or collide with a file ancestor. contents creates one UTF-8 text file. Success persists one complete-tree KTO revision and emits one complete Named Tool Message titled File: <path>."#;
32const WEB_CODE_RENAME_FILE: &str = r#"Invoke as WebCodeRenameFile with arguments matching exactly this JSON Schema: {"type":"object","properties":{"box_id":{"type":"integer","minimum":1},"path":{"type":"string"}},"required":["box_id","path"],"additionalProperties":false}. Example: {"box_id":42,"path":"assets/renamed.png"}. box_id targets one current text or non-text file in the active Web source. path is the new safe slash-relative destination and must not already exist or collide with a file ancestor. Rename preserves exact bytes, never overwrites, persists one complete-tree KTO revision, retires the old file target while retaining its box as a workspace anchor, and emits one complete Named Tool Message for the destination. A non-text destination body is exactly this file is a non-text object and cannot be modified."#;
33const WEB_CODE_DELETE_FILE: &str = r#"Invoke as WebCodeDeleteFile 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 targets one current text or non-text file in the active Web source. Success deletes the exact file, persists one complete-tree KTO revision, and retires that file target while retaining its box as a workspace anchor. It emits no replacement file message. A retired box cannot be deleted again."#;
34const WEB_CODE_CHECK: &str = r#"Invoke as WebCodeCheck 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 may be any source box from the active Create/Open generation, including a retired file box. Every call projects and validates the complete tree and runs a fresh Chromium check. Drafts use an internal deterministic check version. Check does not mutate source or publish. Structured diagnostics use readable lossy UTF-8 rather than decimal byte arrays and model-facing failure text is bounded to 256 KiB with explicit head/tail omission."#;
35const WEB_CODE_PUBLISH: &str = r#"Invoke as WebCodePublish 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 may be any source box from the active Create/Open generation, including a retired file box. version is a canonical stable SemVer unused in that authority/library family. K1 projects the complete exact tree at that version, performs complete package validation and a fresh check, preserves the exact text and binary source Object, reauthorizes disclosure, and submits one immutable public release. Model-facing failure text is bounded to 256 KiB. It does not deploy or restart."#;
36
37static CATALOG: [Entry; 30] = [
38 Entry::new(
39 "KtoolDocs",
40 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."#,
41 ),
42 Entry::new(
43 "CurrentTime",
44 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."#,
45 ),
46 Entry::new(
47 "KmapCreateNode",
48 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."#,
49 ),
50 Entry::new(
51 "KmapOpenNode",
52 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."#,
53 ),
54 Entry::new(
55 "KmapUpdateNode",
56 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."#,
57 ),
58 Entry::new(
59 "KmapPenalizeNodes",
60 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."#,
61 ),
62 Entry::new(
63 "KmapConnectNodes",
64 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."#,
65 ),
66 Entry::new(
67 "SendMessage",
68 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."#,
69 ),
70 Entry::new(
71 "WebSearch",
72 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."#,
73 ),
74 Entry::new(
75 "GetLaunchNode",
76 r#"Invoke as GetLaunchNode with arguments matching exactly this JSON Schema: {"type":"object","properties":{"authority_kind":{"type":"string","enum":["user","group"]},"authority_id":{"type":"string"},"target":{"type":"string"}},"required":["authority_kind","authority_id","target"],"additionalProperties":false}. Example: {"authority_kind":"user","authority_id":"0123456789abcdef01234567","target":"KmapLaunchNode"}. authority_id is one canonical 24-lowercase-hex UserId or GroupId and target is exact and case-sensitive. The caller's active Access context independently authorizes the selected authority's launch binding and referenced Kmap node. Success returns compact JSON {"node_id":"<visible 24-lowercase-hex AccessId>"}. It does not load, create, set, delete, list, search, or otherwise mutate nodes or bindings."#,
77 ),
78 Entry::new(
79 "SetLaunchNode",
80 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."#,
81 ),
82 Entry::new(
83 "ListContacts",
84 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."#,
85 ),
86 Entry::new(
87 "ListGroups",
88 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."#,
89 ),
90 Entry::new(
91 "GetGroup",
92 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."#,
93 ),
94 Entry::new("RustCodeCreate", RUST_CODE_CREATE),
95 Entry::new("RustCodeDocs", RUST_CODE_DOCS),
96 Entry::new("RustCodeOpen", RUST_CODE_OPEN),
97 Entry::new("RustCodeOverwrite", RUST_CODE_OVERWRITE),
98 Entry::new("RustCodeCheck", RUST_CODE_CHECK),
99 Entry::new("RustCodePublish", RUST_CODE_PUBLISH),
100 Entry::new("RustCodeBuild", RUST_CODE_BUILD),
101 Entry::new("WebCodeCreate", WEB_CODE_CREATE),
102 Entry::new("WebCodeDocs", WEB_CODE_DOCS),
103 Entry::new("WebCodeOpen", WEB_CODE_OPEN),
104 Entry::new("WebCodeOverwrite", WEB_CODE_OVERWRITE),
105 Entry::new("WebCodeCreateFile", WEB_CODE_CREATE_FILE),
106 Entry::new("WebCodeRenameFile", WEB_CODE_RENAME_FILE),
107 Entry::new("WebCodeDeleteFile", WEB_CODE_DELETE_FILE),
108 Entry::new("WebCodeCheck", WEB_CODE_CHECK),
109 Entry::new("WebCodePublish", WEB_CODE_PUBLISH),
110];
111
112#[derive(Deserialize)]
113#[serde(deny_unknown_fields)]
114struct Arguments {
115 name: String,
116}
117
118#[derive(Serialize)]
119struct Response<'a> {
120 name: &'a str,
121 latest_version: &'static str,
122 docs: &'a str,
123 deprecated: bool,
124 replacement: Option<&'a str>,
125}
126
127fn find_entry(name: &str) -> Option<&'static Entry> {
128 CATALOG.iter().find(|entry| entry.name == name)
129}
130
131fn render(entry: &Entry) -> Result<String, String> {
132 serde_json::to_string(&Response {
133 name: entry.name,
134 latest_version: "1.0.0",
135 docs: entry.docs,
136 deprecated: false,
137 replacement: None,
138 })
139 .map_err(|error| format!("KtoolDocs response serialization failed: {error}"))
140}
141
142pub fn is_known_ktool(name: &str) -> bool {
143 find_entry(name).is_some()
144}
145
146pub fn ktool_docs(arguments: &str) -> Result<String, String> {
147 let arguments: Arguments =
148 serde_json::from_str(arguments).map_err(|_| INVALID_ARGUMENTS.to_owned())?;
149 if arguments.name.is_empty() {
150 return Err(INVALID_ARGUMENTS.into());
151 }
152 render(find_entry(&arguments.name).ok_or_else(|| UNKNOWN_KTOOL.to_owned())?)
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use serde_json::Value;
159
160 fn query(name: &str) -> String {
161 serde_json::json!({"name": name}).to_string()
162 }
163
164 #[test]
165 fn catalog_is_exact_and_remains_v1() {
166 assert_eq!(CATALOG.len(), 30);
167 for entry in &CATALOG {
168 assert!(is_known_ktool(entry.name));
169 let value: Value =
170 serde_json::from_str(&ktool_docs(&query(entry.name)).unwrap()).unwrap();
171 assert_eq!(value.as_object().unwrap().len(), 5);
172 assert_eq!(value["latest_version"], "1.0.0");
173 assert_eq!(value["deprecated"], false);
174 assert!(value["replacement"].is_null());
175 }
176 let get = ktool_docs(&query("GetLaunchNode")).unwrap();
177 assert!(get.contains("authority_kind"));
178 assert!(get.contains("visible 24-lowercase-hex AccessId"));
179 assert!(get.contains("does not load"));
180 }
181
182 #[test]
183 fn code_contracts_freeze_persistent_selector_semantics() {
184 assert!(RUST_CODE_CREATE.contains(r#""required":["library"]"#));
185 assert!(RUST_CODE_OVERWRITE.contains("Every successful call persists"));
186 assert!(RUST_CODE_PUBLISH.contains("declared only at publication"));
187 assert!(RUST_CODE_BUILD.contains(r#""required":["library","version"]"#));
188 assert!(RUST_CODE_BUILD.contains("cargo build --release --locked"));
189 assert!(RUST_CODE_BUILD.contains("creates no Object"));
190
191 assert!(
192 WEB_CODE_CREATE.contains("Documentation.md plus index.js, index.html, or index.css")
193 );
194 assert!(WEB_CODE_OPEN.contains("one complete Named Tool Message"));
195 assert!(WEB_CODE_OPEN.contains("this revision performs no chunking"));
196 assert!(WEB_CODE_OVERWRITE.contains("Non-UTF-8 targets fail"));
197 assert!(WEB_CODE_CREATE_FILE.contains(r#""required":["box_id","path","contents"]"#));
198 assert!(WEB_CODE_CREATE_FILE.contains("workspace rather than naming the destination"));
199 assert!(WEB_CODE_RENAME_FILE.contains(r#""required":["box_id","path"]"#));
200 assert!(WEB_CODE_RENAME_FILE.contains("never overwrites"));
201 assert!(WEB_CODE_DELETE_FILE.contains(r#""required":["box_id"]"#));
202 assert!(WEB_CODE_DELETE_FILE.contains("text or non-text"));
203 assert!(WEB_CODE_CHECK.contains("256 KiB"));
204 assert!(WEB_CODE_PUBLISH.contains("text and binary source Object"));
205 for docs in [WEB_CODE_OPEN, WEB_CODE_OVERWRITE, WEB_CODE_RENAME_FILE] {
206 assert!(docs.contains("this file is a non-text object and cannot be modified"));
207 }
208 for docs in [
209 WEB_CODE_CREATE,
210 WEB_CODE_DOCS,
211 WEB_CODE_OPEN,
212 RUST_CODE_BUILD,
213 ] {
214 assert!(!docs.contains(r#""authority":{"type""#));
215 }
216 }
217
218 #[test]
219 fn malformed_and_unknown_arguments_fail_closed() {
220 for input in [
221 "",
222 "null",
223 "[]",
224 "{}",
225 r#"{"name":""}"#,
226 r#"{"name":1}"#,
227 r#"{"name":"KtoolDocs","extra":false}"#,
228 r#"{"name":"KtoolDocs","name":"CurrentTime"}"#,
229 ] {
230 assert_eq!(ktool_docs(input), Err(INVALID_ARGUMENTS.into()));
231 }
232 assert_eq!(ktool_docs(&query("Unknown")), Err(UNKNOWN_KTOOL.into()));
233 assert!(!is_known_ktool(" WebSearch"));
234 }
235}