Skip to main content

assay/
context.rs

1/// Context output formatter for module metadata.
2/// Renders prompt-ready Markdown text from module metadata.
3
4#[derive(Debug, Clone)]
5pub struct ModuleContextEntry {
6    pub module_name: String,
7    pub description: String,
8    pub env_vars: Vec<String>,
9    pub quickrefs: Vec<QuickRefEntry>,
10}
11
12#[derive(Debug, Clone)]
13pub struct QuickRefEntry {
14    pub signature: String,
15    pub return_hint: String,
16    pub description: String,
17}
18
19/// Format module context entries into prompt-ready Markdown: the module list
20/// with descriptions, env vars and method signatures, followed by the built-in
21/// functions reference. Lines are kept under 120 chars where practical.
22pub fn format_context(entries: &[ModuleContextEntry]) -> String {
23    render(entries, true)
24}
25
26/// [`format_context`] without the built-in functions reference, for a caller
27/// whose context already carries it.
28pub fn format_context_without_builtins(entries: &[ModuleContextEntry]) -> String {
29    render(entries, false)
30}
31
32fn render(entries: &[ModuleContextEntry], include_builtins: bool) -> String {
33    let mut output = String::new();
34
35    output.push_str("# Assay Module Context\n\n");
36
37    if entries.is_empty() {
38        output.push_str("No matching modules found.\n\n");
39    } else {
40        output.push_str("## Matching Modules\n\n");
41
42        for entry in entries {
43            output.push_str(&format!("### {}\n", entry.module_name));
44            output.push_str(&format!("{}\n", entry.description));
45
46            if !entry.env_vars.is_empty() {
47                output.push_str(&format!("Env: {}\n", entry.env_vars.join(", ")));
48            }
49
50            if !entry.quickrefs.is_empty() {
51                output.push_str("Methods:\n");
52                for qr in &entry.quickrefs {
53                    output.push_str(&format!(
54                        "  {} -> {} | {}\n",
55                        qr.signature, qr.return_hint, qr.description
56                    ));
57                }
58            }
59
60            output.push('\n');
61        }
62    }
63
64    if include_builtins {
65        push_builtins(&mut output);
66    }
67
68    output
69}
70
71fn push_builtins(output: &mut String) {
72    output.push_str("## Built-in Functions (always available, no require needed)\n");
73    output.push_str("http.get(url, opts?) -> {status, body, headers}\n");
74    output.push_str("json.parse(str) -> table | json.encode(tbl) -> str\n");
75    output.push_str(
76        "yaml.parse(str) -> table | yaml.parse_all(str) -> [table] | yaml.encode(tbl) -> str\n",
77    );
78    output.push_str("toml.parse(str) -> table | toml.encode(tbl) -> str\n");
79    output.push_str("base64.encode(str) -> str | base64.decode(str) -> str\n");
80    output.push_str("crypto.jwt_sign(claims, key, alg) -> token\n");
81    output.push_str("crypto.hash(str, alg) -> str | crypto.hmac(key, data, alg?) -> str\n");
82    output.push_str("crypto.random(len) -> str\n");
83    output.push_str("regex.match(pat, str) -> bool | regex.find(pat, str) -> str\n");
84    output.push_str("regex.find_all(pat, str) -> [str] | regex.replace(pat, str, repl) -> str\n");
85    output.push_str("fs.read(path) -> str | fs.write(path, str)\n");
86    output.push_str("db.connect(url) -> conn | db.query(conn, sql, params?) -> [row]\n");
87    output.push_str("db.execute(conn, sql, params?) -> count | db.close(conn)\n");
88    output.push_str("ws.connect(url) -> conn | ws.send(conn, msg)\n");
89    output.push_str("ws.recv(conn) -> msg | ws.close(conn)\n");
90    output.push_str("template.render(path, vars) -> str\n");
91    output.push_str("template.render_string(tmpl, vars) -> str\n");
92    output.push_str("async.spawn(fn) -> handle | async.spawn_interval(fn, ms) -> handle\n");
93    output.push_str("handle:await() | handle:cancel()\n");
94    output.push_str("assert.eq(a, b, msg?) | assert.gt(a, b, msg?) | assert.lt(a, b, msg?)\n");
95    output.push_str("assert.contains(str, sub, msg?) | assert.not_nil(val, msg?)\n");
96    output.push_str("assert.matches(str, pat, msg?)\n");
97    output.push_str("log.info(msg) | log.warn(msg) | log.error(msg)\n");
98    output.push_str("env.get(key) -> str | sleep(secs) | time() -> int\n");
99}