Skip to main content

submilli_engine/stdlib/skills/
declaration.rs

1//! Type surface of `submilli:skills`.
2
3use std::collections::BTreeMap;
4
5use crate::{
6    Dispatch, PackageDeclaration, Param, PropertySig, Span, Type, TypeKind, TypeSymbol, ValueKind,
7    ValueSymbol,
8};
9
10use super::MODULE_NAME;
11
12pub fn package_declaration() -> PackageDeclaration {
13    let mut defs = PackageDeclaration::with_package(MODULE_NAME);
14    insert_interface(
15        &mut defs,
16        "SkillInfo",
17        skill_properties(false),
18        "/** One skill this caller may load. Constructed only by `list()`. */",
19    );
20    insert_interface(
21        &mut defs,
22        "Skill",
23        skill_properties(true),
24        "/** A loaded skill: its instructions, and the name to read its files under. Constructed only by `load()`. */",
25    );
26    insert_fn(
27        &mut defs,
28        "list",
29        Vec::new(),
30        Type::Array(Box::new(interface_type("SkillInfo"))),
31        "/**\n * The skills this caller may load.\n *\n * Each skill the harness offers is \
32         filtered under `skill.load` by the same `name` filter that gates `load`, so \
33         the list never offers a skill the caller would be denied. Nothing in it \
34         reveals how many were filtered out.\n * @capability skill.load\n */",
35    );
36    insert_fn(
37        &mut defs,
38        "load",
39        vec![Param::new("name", Type::String)],
40        interface_type("Skill"),
41        "/**\n * Load the skill `name`: its description and its instructions.\n *\n * \
42         A name that is not one skill name — empty, `.` or `..`, containing `/`, \
43         `\\` or NUL, or over 4096 bytes — throws a `RangeError` without asking the \
44         harness. Throws a catchable error when the harness has no skill of that name, \
45         or when this runtime has no skill provider.\n * @param name Skill name, as \
46         `list()` returns it.\n * @capability skill.load { name: $name }\n */",
47    );
48    insert_fn(
49        &mut defs,
50        "readFile",
51        vec![
52            Param::new("name", Type::String),
53            Param::new("path", Type::String),
54        ],
55        Type::String,
56        "/**\n * Read a file bundled with the skill `name`, as text.\n *\n * `path` is \
57         relative to the skill, with `/` separators, as the skill's instructions name \
58         it — `\"templates/report.md\"`. An absolute path, one with an empty, `.` \
59         or `..` segment, one containing `\\`, `:` or NUL, or one over 4096 bytes, \
60         throws a `RangeError` \
61         without asking the harness, as does a name that is not one skill name; a file \
62         the skill does not have throws a catchable error.\n * @param name Skill name, \
63         as `list()` returns it.\n \
64         * @param path The file's path inside the skill.\n * @capability skill.load { \
65         name: $name }\n */",
66    );
67    defs
68}
69
70fn interface_type(name: &str) -> Type {
71    Type::InterfaceRef {
72        mangled: crate::mangle::package_symbol(MODULE_NAME, name),
73        package: crate::Package(MODULE_NAME.to_string()),
74        name: name.to_string(),
75        args: Vec::new(),
76    }
77}
78
79/// `name` and `description`, plus `content` for a loaded skill.
80fn skill_properties(loaded: bool) -> BTreeMap<String, PropertySig> {
81    let mut properties = BTreeMap::new();
82    insert_property(
83        &mut properties,
84        "name",
85        Type::String,
86        "/** The skill name, exactly as `load` and `readFile` expect it. */",
87    );
88    insert_property(
89        &mut properties,
90        "description",
91        Type::union(vec![Type::String, Type::Undefined]),
92        "/** What the skill is for, or `undefined` when the harness gives none. */",
93    );
94    if loaded {
95        insert_property(
96            &mut properties,
97            "content",
98            Type::String,
99            "/** The skill's instructions. */",
100        );
101    }
102    properties
103}
104
105fn insert_interface(
106    defs: &mut PackageDeclaration,
107    name: &str,
108    properties: BTreeMap<String, PropertySig>,
109    doc: &str,
110) {
111    defs.types.insert(
112        name.to_string(),
113        TypeSymbol {
114            name: name.to_string(),
115            mangled_name: crate::mangle::package_symbol(MODULE_NAME, name),
116            declaration_span: Span::at(crate::FileId::SKILLS),
117            kind: TypeKind::Interface {
118                index: None,
119                generics: Vec::new(),
120                methods: BTreeMap::new(),
121                properties,
122                dispatch: Dispatch::Direct,
123                doc: crate::doc(crate::FileId::SKILLS, doc),
124            },
125        },
126    );
127}
128
129fn insert_property(
130    properties: &mut BTreeMap<String, PropertySig>,
131    name: &str,
132    ty: Type,
133    doc: &str,
134) {
135    properties.insert(
136        name.to_string(),
137        PropertySig {
138            ty,
139            readonly: true,
140            optional: false,
141            intrinsic: false,
142            doc: crate::doc(crate::FileId::SKILLS, doc),
143        },
144    );
145}
146
147fn insert_fn(defs: &mut PackageDeclaration, name: &str, params: Vec<Param>, ret: Type, doc: &str) {
148    defs.values.insert(
149        name.to_string(),
150        ValueSymbol {
151            name: name.to_string(),
152            mangled_name: crate::mangle::package_symbol(MODULE_NAME, name),
153            declaration_span: Span::at(crate::FileId::SKILLS),
154            kind: ValueKind::Function {
155                generics: Vec::new(),
156                params,
157                ret,
158                type_predicate: None,
159                doc: crate::doc(crate::FileId::SKILLS, doc),
160            },
161        },
162    );
163}