submilli_engine/stdlib/skills/
declaration.rs1use std::collections::BTreeMap;
4
5use crate::{
6 Dispatch, MethodSig, PackageDeclaration, Param, PropertySig, Span, Type, TypeKind, TypeSymbol,
7 ValueKind, 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 BTreeMap::new(),
19 "/** One skill this caller may load. Constructed only by `list()`. */",
20 );
21 insert_interface(
22 &mut defs,
23 "Skill",
24 skill_properties(true),
25 skill_methods(),
26 "/** A loaded skill. Read bundled files with `skill.readFile(path)`. Skills are never mounted or accessible through `submilli:fs`. Constructed only by `load()`. */",
27 );
28 insert_fn(
29 &mut defs,
30 "list",
31 Vec::new(),
32 Type::Array(Box::new(interface_type("SkillInfo"))),
33 "/**\n * The skills this caller may load.\n *\n * Each skill the harness offers is \
34 filtered under `skill.load` by the same `name` filter that gates `load`, so \
35 the list never offers a skill the caller would be denied. Nothing in it \
36 reveals how many were filtered out.\n * @capability skill.load\n */",
37 );
38 insert_fn(
39 &mut defs,
40 "load",
41 vec![Param::new("name", Type::String)],
42 interface_type("Skill"),
43 "/**\n * Load the skill `name`: its description, instructions and bundled file names.\n *\n * \
44 A name that is not one skill name — empty, `.` or `..`, containing `/`, \
45 `\\` or NUL, or over 4096 bytes — throws a `RangeError` without asking the \
46 harness. Throws a catchable error when the harness has no skill of that name, \
47 or when this runtime has no skill provider.\n * @param name Skill name, as \
48 `list()` returns it.\n * @capability skill.load { name: $name }\n */",
49 );
50 defs
51}
52
53fn skill_methods() -> BTreeMap<String, MethodSig> {
54 BTreeMap::from([(
55 "readFile".to_string(),
56 MethodSig {
57 optional: false,
58 generics: Vec::new(),
59 params: vec![Param::new("path", Type::String)],
60 ret: Type::String,
61 predicate: None,
62 doc: crate::doc(
63 crate::FileId::SKILLS,
64 "/** Read a bundled file as text. Pass a path from `files`, relative to this skill with `/` separators. Absolute paths, empty, `.` or `..` segments, `\\`, `:` or NUL, and paths over 4096 bytes throw a `RangeError` before asking the provider. A missing file throws a catchable error. Files are never accessible through `submilli:fs`. The `skill.load` policy is checked again using this skill's name.\n * @param path The file's path inside this skill.\n * @capability skill.load\n */",
65 ),
66 },
67 )])
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 "files",
98 Type::Array(Box::new(Type::String)),
99 "/** Bundled file names relative to this skill, without reading their contents. Pass each to `skill.readFile(path)`. These are not filesystem paths and cannot be read through `submilli:fs`. Invalid paths are omitted. */",
100 );
101 insert_property(
102 &mut properties,
103 "content",
104 Type::String,
105 "/** The skill's instructions. */",
106 );
107 }
108 properties
109}
110
111fn insert_interface(
112 defs: &mut PackageDeclaration,
113 name: &str,
114 properties: BTreeMap<String, PropertySig>,
115 methods: BTreeMap<String, MethodSig>,
116 doc: &str,
117) {
118 defs.types.insert(
119 name.to_string(),
120 TypeSymbol {
121 name: name.to_string(),
122 mangled_name: crate::mangle::package_symbol(MODULE_NAME, name),
123 declaration_span: Span::at(crate::FileId::SKILLS),
124 kind: TypeKind::Interface {
125 index: None,
126 generics: Vec::new(),
127 methods,
128 properties,
129 dispatch: Dispatch::Direct,
130 doc: crate::doc(crate::FileId::SKILLS, doc),
131 },
132 },
133 );
134}
135
136fn insert_property(
137 properties: &mut BTreeMap<String, PropertySig>,
138 name: &str,
139 ty: Type,
140 doc: &str,
141) {
142 properties.insert(
143 name.to_string(),
144 PropertySig {
145 ty,
146 readonly: true,
147 optional: false,
148 intrinsic: false,
149 doc: crate::doc(crate::FileId::SKILLS, doc),
150 },
151 );
152}
153
154fn insert_fn(defs: &mut PackageDeclaration, name: &str, params: Vec<Param>, ret: Type, doc: &str) {
155 defs.values.insert(
156 name.to_string(),
157 ValueSymbol {
158 name: name.to_string(),
159 mangled_name: crate::mangle::package_symbol(MODULE_NAME, name),
160 declaration_span: Span::at(crate::FileId::SKILLS),
161 kind: ValueKind::Function {
162 generics: Vec::new(),
163 params,
164 ret,
165 type_predicate: None,
166 doc: crate::doc(crate::FileId::SKILLS, doc),
167 },
168 },
169 );
170}