#![forbid(unsafe_code)]
use serde::{Deserialize, Serialize};
const INVALID_ARGUMENTS: &str = "invalid KtoolDocs arguments";
const UNKNOWN_KTOOL: &str = "unknown Ktool";
struct Entry {
name: &'static str,
version: &'static str,
docs: &'static str,
replacement: Option<&'static str>,
}
impl Entry {
const fn new(
name: &'static str,
version: &'static str,
docs: &'static str,
replacement: Option<&'static str>,
) -> Self {
Self {
name,
version,
docs,
replacement,
}
}
}
static CATALOG: [Entry; 8] = [
Entry::new(
"KtoolDocs",
"1.0.0",
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 a compact JSON object 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."#,
None,
),
Entry::new(
"CurrentTime",
"1.0.0",
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."#,
None,
),
Entry::new(
"KmapCreateNode",
"1.0.0",
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."#,
None,
),
Entry::new(
"KmapOpenNode",
"1.0.0",
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."#,
None,
),
Entry::new(
"KmapUpdateNode",
"1.0.0",
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."#,
None,
),
Entry::new(
"KmapPenalizeNodes",
"1.0.0",
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."#,
None,
),
Entry::new(
"KmapConnectNodes",
"1.0.0",
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."#,
None,
),
Entry::new(
"SendMessage",
"1.0.0",
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."#,
None,
),
];
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Arguments {
name: String,
}
#[derive(Serialize)]
struct Response<'a> {
name: &'a str,
latest_version: &'static str,
docs: &'a str,
deprecated: bool,
replacement: Option<&'a str>,
}
fn find_entry<'a>(catalog: &'a [Entry], name: &str) -> Option<&'a Entry> {
catalog.iter().find(|entry| entry.name == name)
}
fn render(entry: &Entry) -> Result<String, String> {
let response = Response {
name: entry.name,
latest_version: entry.version,
docs: entry.docs,
deprecated: entry.replacement.is_some(),
replacement: entry.replacement,
};
serde_json::to_string(&response)
.map_err(|error| format!("KtoolDocs response serialization failed: {error}"))
}
/// Looks up the exact Ktool name supplied in a strict JSON argument object.
pub fn ktool_docs(arguments: &str) -> Result<String, String> {
let arguments: Arguments =
serde_json::from_str(arguments).map_err(|_| INVALID_ARGUMENTS.to_owned())?;
if arguments.name.is_empty() {
return Err(INVALID_ARGUMENTS.to_owned());
}
let entry = find_entry(&CATALOG, &arguments.name).ok_or_else(|| UNKNOWN_KTOOL.to_owned())?;
render(entry)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::Value;
const EXPECTED: [(&str, &str); 8] = [
(
"KtoolDocs",
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 a compact JSON object 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."#,
),
(
"CurrentTime",
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."#,
),
(
"KmapCreateNode",
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."#,
),
(
"KmapOpenNode",
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."#,
),
(
"KmapUpdateNode",
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."#,
),
(
"KmapPenalizeNodes",
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."#,
),
(
"KmapConnectNodes",
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."#,
),
(
"SendMessage",
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."#,
),
];
fn arguments_for(name: &str) -> String {
format!(
"{{\"name\":{}}}",
serde_json::to_string(name).expect("a string is JSON-serializable")
)
}
fn expected_active_json(name: &str, docs: &str) -> String {
format!(
"{{\"name\":{},\"latest_version\":\"1.0.0\",\"docs\":{},\"deprecated\":false,\"replacement\":null}}",
serde_json::to_string(name).expect("a string is JSON-serializable"),
serde_json::to_string(docs).expect("a string is JSON-serializable")
)
}
fn replacement_invariants_hold(catalog: &[Entry]) -> bool {
catalog.iter().all(|entry| {
let Some(replacement) = entry.replacement else {
return true;
};
if replacement.is_empty() || replacement == entry.name {
return false;
}
matches!(
find_entry(catalog, replacement),
Some(target) if target.replacement.is_none()
)
})
}
#[test]
fn all_exact_names_have_exact_ordered_five_field_outputs() {
for (name, docs) in EXPECTED {
let output = ktool_docs(&arguments_for(name)).expect("known name must succeed");
assert_eq!(output, expected_active_json(name, docs));
let value: Value = serde_json::from_str(&output).expect("response must be JSON");
let object = value.as_object().expect("response must be an object");
assert_eq!(object.len(), 5);
assert_eq!(object.get("name").and_then(Value::as_str), Some(name));
assert_eq!(
object.get("latest_version").and_then(Value::as_str),
Some("1.0.0")
);
assert_eq!(object.get("docs").and_then(Value::as_str), Some(docs));
assert_eq!(
object.get("deprecated").and_then(Value::as_bool),
Some(false)
);
assert!(object.get("replacement").is_some_and(Value::is_null));
}
}
#[test]
fn self_lookup_documents_ktool_docs() {
let output = ktool_docs(r#"{"name":"KtoolDocs"}"#).expect("self lookup must succeed");
assert_eq!(output, expected_active_json(EXPECTED[0].0, EXPECTED[0].1));
}
#[test]
fn malformed_or_nonconforming_arguments_are_strictly_rejected() {
let invalid = [
"",
" ",
"{",
"null",
"[]",
r#""KtoolDocs""#,
"0",
"true",
"{}",
r#"{"other":"KtoolDocs"}"#,
r#"{"Name":"KtoolDocs"}"#,
r#"{"name":null}"#,
r#"{"name":true}"#,
r#"{"name":1}"#,
r#"{"name":[]}"#,
r#"{"name":{}}"#,
r#"{"name":""}"#,
r#"{"name":"KtoolDocs","extra":false}"#,
r#"{"extra":false,"name":"KtoolDocs"}"#,
r#"{"name":"KtoolDocs","name":"CurrentTime"}"#,
r#"{"name":"KtoolDocs"} trailing"#,
];
for input in invalid {
assert_eq!(
ktool_docs(input),
Err(INVALID_ARGUMENTS.to_owned()),
"{input:?}"
);
}
}
#[test]
fn exact_match_variants_return_only_unknown_ktool() {
let variants = [
"ktoolDocs",
"KTOOLDOCS",
" KtoolDocs",
"KtoolDocs ",
"KtoolDoc",
"KtoolDocs\n",
"Currenttime",
"KmapOpenNode/",
"SendMessage\0",
];
for variant in variants {
assert_eq!(
ktool_docs(&arguments_for(variant)),
Err(UNKNOWN_KTOOL.to_owned()),
"{variant:?}"
);
}
}
#[test]
fn repeated_lookups_are_deterministic() {
for (name, _) in EXPECTED {
let arguments = arguments_for(name);
let first = ktool_docs(&arguments).expect("known name must succeed");
for _ in 0..10 {
assert_eq!(ktool_docs(&arguments), Ok(first.clone()));
}
}
}
#[test]
fn rendering_escapes_all_json_string_fields() {
let entry = Entry::new(
"quote\" slash\\ newline\n",
"7.4.2",
"tab\t backspace\u{0008} quote\" slash\\",
None,
);
let output = render(&entry).expect("supported response must serialize");
assert!(!output.contains('\n'));
assert!(!output.contains('\t'));
assert!(!output.contains('\u{0008}'));
assert!(output.contains("\\n"));
assert!(output.contains("\\t"));
assert!(output.contains("\\b"));
assert!(output.contains("\\\""));
assert!(output.contains("\\\\"));
let value: Value = serde_json::from_str(&output).expect("escaped response must parse");
assert_eq!(value.get("name").and_then(Value::as_str), Some(entry.name));
assert_eq!(value.get("docs").and_then(Value::as_str), Some(entry.docs));
}
#[test]
fn private_deprecated_entry_renders_replacement_and_derived_flag() {
let entry = Entry::new("RetiredTool", "2.1.3", "Retired docs.", Some("CurrentTime"));
let output = render(&entry).expect("deprecated response must serialize");
assert_eq!(
output,
r#"{"name":"RetiredTool","latest_version":"2.1.3","docs":"Retired docs.","deprecated":true,"replacement":"CurrentTime"}"#
);
let value: Value = serde_json::from_str(&output).expect("response must parse");
assert_eq!(
value.get("deprecated").and_then(Value::as_bool),
Some(entry.replacement.is_some())
);
}
#[test]
fn replacement_invariants_require_nonempty_nonself_known_active_target() {
let valid = [
Entry::new("NewTool", "3.0.0", "new", None),
Entry::new("OldTool", "2.0.0", "old", Some("NewTool")),
];
assert!(replacement_invariants_hold(&CATALOG));
assert!(replacement_invariants_hold(&valid));
let empty = [Entry::new("OldTool", "1.0.0", "old", Some(""))];
let self_replacement = [Entry::new("OldTool", "1.0.0", "old", Some("OldTool"))];
let unknown = [Entry::new("OldTool", "1.0.0", "old", Some("MissingTool"))];
let deprecated_target = [
Entry::new("NewTool", "3.0.0", "new", None),
Entry::new("MiddleTool", "2.0.0", "middle", Some("NewTool")),
Entry::new("OldTool", "1.0.0", "old", Some("MiddleTool")),
];
assert!(!replacement_invariants_hold(&empty));
assert!(!replacement_invariants_hold(&self_replacement));
assert!(!replacement_invariants_hold(&unknown));
assert!(!replacement_invariants_hold(&deprecated_target));
}
}