Skip to main content

submilli_engine/stdlib/code/
declaration.rs

1//! Agent-facing declarations. Structural records remain serializable guest objects.
2use super::MODULE_NAME;
3use crate::{
4    DefaultValue, FileId, ObjectField, PackageDeclaration, Param, Span, Type, ValueKind,
5    ValueSymbol,
6};
7
8pub fn package_declaration() -> PackageDeclaration {
9    let mut defs = PackageDeclaration::with_package(MODULE_NAME);
10    let line = object(&[("line", Type::Number), ("text", Type::String)], false);
11    let diagnostic = object(
12        &[
13            ("hunk", Type::Number),
14            ("line", Type::Number),
15            ("message", Type::String),
16        ],
17        false,
18    );
19    let mutation = object(
20        &[
21            ("success", Type::Boolean),
22            ("changed", Type::Boolean),
23            ("diff", Type::String),
24            ("diagnostics", array(diagnostic)),
25        ],
26        false,
27    );
28    insert(
29        &mut defs,
30        "read",
31        vec![
32            string("path"),
33            number("offset", 1.0),
34            number("limit", 200.0),
35        ],
36        object(
37            &[
38                ("path", Type::String),
39                ("lines", array(line.clone())),
40                ("truncated", Type::Boolean),
41            ],
42            false,
43        ),
44        "/** Numbered UTF-8 lines; one-based offset, default 200 lines. Requires fs.read. */",
45    );
46    let options = object(
47        &[
48            ("path", Type::String),
49            ("include", array(Type::String)),
50            ("exclude", array(Type::String)),
51            ("caseSensitive", Type::Boolean),
52            ("context", Type::Number),
53            ("mode", Type::String),
54            ("limit", Type::Number),
55        ],
56        true,
57    );
58    let hit = object(
59        &[
60            ("path", Type::String),
61            ("line", Type::Number),
62            ("text", Type::String),
63            ("before", array(line.clone())),
64            ("after", array(line)),
65        ],
66        false,
67    );
68    let count = object(&[("path", Type::String), ("count", Type::Number)], false);
69    insert(
70        &mut defs,
71        "search",
72        vec![
73            string("pattern"),
74            Param::with_default(
75                "options",
76                Type::union(vec![options, Type::Undefined]),
77                DefaultValue::Undefined,
78            ),
79        ],
80        object(
81            &[
82                ("matches", array(hit)),
83                ("files", array(Type::String)),
84                ("counts", array(count)),
85                ("truncated", Type::Boolean),
86            ],
87            false,
88        ),
89        "/** Regex search, ignoring hidden and ignored paths. Modes: matches (default), files, counts. Root defaults to /; context 0; caseSensitive true; limit 1000. Requires fs.list, fs.stat and fs.read, including ignore files; those above the root are skipped where fs.read is denied. */",
90    );
91    let entry = object(
92        &[
93            ("path", Type::String),
94            ("kind", Type::String),
95            ("depth", Type::Number),
96            ("modifiedAt", Type::Number),
97        ],
98        false,
99    );
100    let listing = object(
101        &[("entries", array(entry)), ("truncated", Type::Boolean)],
102        false,
103    );
104    insert(
105        &mut defs,
106        "glob",
107        vec![string("pattern")],
108        listing.clone(),
109        "/** Match file paths relative to /; newest modification first, path breaks ties. Maximum 1000 results. Requires fs.list, fs.stat and fs.read for ignore files. */",
110    );
111    insert(
112        &mut defs,
113        "tree",
114        vec![string("path"), number("depth", 3.0)],
115        listing,
116        "/** Ignored/hidden paths omitted; symlinks listed but never traversed. Depth defaults to 3; maximum 1000 entries. Requires fs.list, fs.stat and fs.read for ignore files; those above the root are skipped where fs.read is denied. */",
117    );
118    insert(
119        &mut defs,
120        "edit",
121        vec![
122            string("path"),
123            string("oldString"),
124            string("newString"),
125            Param::with_default("replaceAll", Type::Boolean, DefaultValue::Boolean(false)),
126            number("nearLine", 0.0),
127        ],
128        mutation.clone(),
129        "/** Replace a unique exact anchor; replaceAll selects every occurrence. nearLine chooses the uniquely nearest anchor (0 means absent). Whitespace-only whole-line near matches may preserve indentation. Requires fs.read and fs.write. */",
130    );
131    insert(
132        &mut defs,
133        "insertAt",
134        vec![
135            string("path"),
136            Param::new("line", Type::Number),
137            string("text"),
138        ],
139        mutation.clone(),
140        "/** Insert text literally before a one-based line; one past the last line appends. Requires fs.read and fs.write. */",
141    );
142    insert(
143        &mut defs,
144        "diffText",
145        vec![string("a"), string("b")],
146        Type::String,
147        "/** Pure UTF-16 text comparison; unified diff with three context lines. No capability needed. */",
148    );
149    insert(
150        &mut defs,
151        "diffFiles",
152        vec![string("a"), string("b")],
153        Type::String,
154        "/** Unified diff of two strict UTF-8 workspace files. Requires fs.read on both. */",
155    );
156    insert(
157        &mut defs,
158        "applyPatch",
159        vec![string("path"), string("patch")],
160        mutation,
161        "/** Apply a single-file unified patch by unique context, ignoring header positions. All hunks must succeed; otherwise nothing is written. Requires fs.read and fs.write. */",
162    );
163    defs
164}
165fn object(fields: &[(&str, Type)], optional: bool) -> Type {
166    Type::Object {
167        index: None,
168        fields: fields
169            .iter()
170            .map(|(name, ty)| {
171                (
172                    (*name).into(),
173                    if optional {
174                        ObjectField::optional(ty.clone())
175                    } else {
176                        ObjectField::required(ty.clone())
177                    },
178                )
179            })
180            .collect(),
181    }
182}
183fn array(ty: Type) -> Type {
184    Type::Array(Box::new(ty))
185}
186fn string(name: &str) -> Param {
187    Param::new(name, Type::String)
188}
189fn number(name: &str, value: f64) -> Param {
190    Param::with_default(name, Type::Number, DefaultValue::Number(value))
191}
192fn insert(defs: &mut PackageDeclaration, name: &str, params: Vec<Param>, ret: Type, doc: &str) {
193    defs.values.insert(
194        name.into(),
195        ValueSymbol {
196            name: name.into(),
197            mangled_name: crate::mangle::package_symbol(MODULE_NAME, name),
198            declaration_span: Span::at(FileId::CODE),
199            kind: ValueKind::Function {
200                generics: vec![],
201                params,
202                ret,
203                type_predicate: None,
204                doc: crate::doc(FileId::CODE, doc),
205            },
206        },
207    );
208}