submilli_engine/stdlib/skills/
declaration.rs1use 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
79fn 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}