use serde_json::Value;
use std::path::Path;
pub(crate) struct ToolDef {
pub(crate) name: String,
pub(crate) description: Option<String>,
pub(crate) input_schema: Value,
}
pub(crate) fn module(tools: &[ToolDef], door: &Path, tool_id: &str) -> String {
let mut out = String::from(PREAMBLE);
out.push_str(&format!(
"DOOR = [{}, \"invoke\"]\nTOOL_ID = {}\n{}",
literal(&door.to_string_lossy()),
literal(tool_id),
RUNTIME
));
for tool in tools.iter().filter(|t| identifier(&t.name)) {
out.push_str(&function(tool));
}
out
}
fn function(tool: &ToolDef) -> String {
let params = parameters(&tool.input_schema);
let named = params
.iter()
.map(|(name, required)| {
if *required {
name.clone()
} else {
format!("{name}=None")
}
})
.collect::<Vec<_>>()
.join(", ");
let signature = if named.is_empty() {
String::new()
} else {
format!("*, {named}")
};
let arguments = params
.iter()
.map(|(name, _)| format!("{name}={name}"))
.collect::<Vec<_>>()
.join(", ");
let doc = tool.description.clone().unwrap_or_else(|| {
format!(
"the {} tool; no description is committed for it.",
tool.name
)
});
format!(
"\n\ndef {name}({signature}):\n {doc}\n return invoke({name_literal}, _arguments({arguments}))\n",
name = tool.name,
doc = literal(&doc),
name_literal = literal(&tool.name),
)
}
fn parameters(schema: &Value) -> Vec<(String, bool)> {
let required: Vec<&str> = schema["required"]
.as_array()
.map(|names| names.iter().filter_map(Value::as_str).collect())
.unwrap_or_default();
let Some(properties) = schema["properties"].as_object() else {
return Vec::new();
};
let mut params: Vec<(String, bool)> = properties
.keys()
.filter(|name| identifier(name))
.map(|name| (name.clone(), required.contains(&name.as_str())))
.collect();
params.sort_by_key(|(_, required)| !*required);
params
}
pub(crate) fn identifier(name: &str) -> bool {
let mut chars = name.chars();
chars
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
fn literal(text: &str) -> String {
Value::String(text.to_string()).to_string()
}
const PREAMBLE: &str = r#""""litany_tools — the tools you may call from this program.
Generated fresh for this one `python` invocation from the calling role's
effective toolset, so what this module offers is what the harness
permits (docs/DESIGN_CODE_EXECUTION.md §2.7). Each function runs ONE
inner invocation through litany's front door: gated, adjudicated and
recorded exactly as a top-level tool call would be, and entering the
model's context nowhere. Only what this program prints on stdout does,
so print what matters and let the rest stay here.
"""
import json
import subprocess
"#;
const RUNTIME: &str = r#"
_counter = 0
class DoorError(RuntimeError):
"""The door could not be reached, so the invocation never ran.
A tool that ran and failed is never this: it comes back as a Result
with a non-zero exit_code, because a program filtering failures
wants the code.
"""
class Result:
"""One inner invocation's result: the tool's raw result envelope,
split into the streams that carry it."""
def __init__(self, stdout, stderr, exit_code):
self.stdout = stdout
self.stderr = stderr
self.exit_code = exit_code
self.ok = exit_code == 0
def __repr__(self):
return "Result(exit_code=%r, ok=%r, stdout=%r, stderr=%r)" % (
self.exit_code,
self.ok,
self.stdout,
self.stderr,
)
def invoke(name, arguments=None):
"""Run one inner invocation of `name` with `arguments`, and return
its Result. This is the general path every generated function below
is sugar over; call it directly for a tool this module does not
name."""
global _counter
_counter += 1
block = json.dumps(
{
"id": "%s-%d" % (TOOL_ID, _counter),
"name": name,
"input": arguments if arguments is not None else {},
}
)
try:
done = subprocess.run(DOOR, input=block, capture_output=True, text=True)
except OSError as exc:
raise DoorError("could not run %r: %s" % (DOOR, exc)) from exc
return Result(done.stdout, done.stderr, done.returncode)
def _arguments(**kwargs):
"""The invocation's input object: every argument the caller named,
with the ones it left unset omitted rather than sent as null."""
return {key: value for key, value in kwargs.items() if value is not None}
"#;
#[cfg(test)]
mod tests;