Skip to main content

submilli_engine/stdlib/
mod.rs

1//! Standard-library packages user code reaches via `import` —
2//! `submilli:crypto` / `submilli:embedding` / `submilli:fs` / `submilli:http` / `submilli:llm` /
3//! `submilli:secrets` / `submilli:security` / `submilli:session` /
4//! `submilli:url` / `submilli:uuid`.
5//!
6//! Every package is pure Rust host functions registered directly under its
7//! package name; the linker resolves user imports with no Wasm shim modules
8//! and no Wasm module instantiation. Host-backed classes initialize their
9//! vtables separately for each store.
10
11pub(crate) mod abi;
12pub mod agents;
13pub mod capabilities;
14pub mod code;
15pub mod crypto;
16pub(crate) mod dot_segments;
17pub mod embedding;
18pub mod fs;
19pub mod git;
20pub mod http;
21pub(crate) mod json_schema;
22pub mod llm;
23pub mod secrets;
24pub mod security;
25pub mod session;
26pub mod shared;
27pub mod skills;
28/// Test-authoring package. Deliberately absent from
29/// [`stdlib_package_declarations`] and [`install_host_functions`]: only
30/// `submilli build test` makes it importable (by passing its declaration into
31/// the compile and installing it directly), so `submilli run` rejects
32/// `import ... from "submilli:test"` as not found.
33pub mod test;
34pub mod url;
35pub mod uuid;
36pub mod variables;
37
38use wasmtime::Linker;
39
40use crate::PackageDeclaration;
41use crate::runtime::StoreData;
42
43/// A standard-library package that exists only when the embedder provides it.
44///
45/// Each one is backed by a provider trait the harness implements. An embedder
46/// that does not enable a package gets none of it: the package cannot be
47/// imported, discovery and the capability catalog do not list it, and its host
48/// functions are not installed.
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
50#[non_exhaustive]
51pub enum OptionalPackage {
52    /// `submilli:agents`, backed by [`AgentProvider`](crate::runtime::AgentProvider).
53    Agents,
54    /// `submilli:skills`, backed by [`SkillProvider`](crate::runtime::SkillProvider).
55    Skills,
56}
57
58impl OptionalPackage {
59    pub const ALL: &'static [OptionalPackage] = &[Self::Agents, Self::Skills];
60
61    pub const fn module_name(self) -> &'static str {
62        match self {
63            Self::Agents => agents::MODULE_NAME,
64            Self::Skills => skills::MODULE_NAME,
65        }
66    }
67
68    pub fn from_module_name(name: &str) -> Option<Self> {
69        Self::ALL
70            .iter()
71            .copied()
72            .find(|package| package.module_name() == name)
73    }
74
75    fn package_declaration(self) -> PackageDeclaration {
76        match self {
77            Self::Agents => agents::package_declaration(),
78            Self::Skills => skills::package_declaration(),
79        }
80    }
81
82    fn install(self, linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
83        match self {
84            Self::Agents => agents::install(linker),
85            Self::Skills => skills::install(linker),
86        }
87    }
88
89    const fn bit(self) -> u8 {
90        match self {
91            Self::Agents => 1,
92            Self::Skills => 1 << 1,
93        }
94    }
95}
96
97/// The standard library an embedder offers its programs: the core packages
98/// every embedder has, plus the [`OptionalPackage`]s it enables.
99///
100/// Compiling, discovery, the capability catalog and host-function installation
101/// all read the same set, so a package is either fully present or fully absent.
102/// The discovery methods (`docs`, `search`, `resolve`, …) are in
103/// [`crate::packages`].
104#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
105pub struct Stdlib {
106    optional: u8,
107}
108
109impl Stdlib {
110    /// The core packages only, what `submilli run` and the server offer.
111    pub const fn core() -> Self {
112        Stdlib { optional: 0 }
113    }
114
115    #[must_use]
116    pub const fn with(self, package: OptionalPackage) -> Self {
117        Stdlib {
118            optional: self.optional | package.bit(),
119        }
120    }
121
122    pub const fn enables(self, package: OptionalPackage) -> bool {
123        self.optional & package.bit() != 0
124    }
125
126    pub fn optional_packages(self) -> impl Iterator<Item = OptionalPackage> {
127        OptionalPackage::ALL
128            .iter()
129            .copied()
130            .filter(move |package| self.enables(*package))
131    }
132
133    /// Whether `name` is a package of this set.
134    pub fn contains_module(self, name: &str) -> bool {
135        OptionalPackage::from_module_name(name).map_or_else(
136            || {
137                core_package_declarations()
138                    .iter()
139                    .any(|defs| defs.package_name == name)
140            },
141            |package| self.enables(package),
142        )
143    }
144
145    /// Every package of this set, sorted by name.
146    pub fn package_declarations(self) -> Vec<PackageDeclaration> {
147        let mut declarations = core_package_declarations();
148        declarations.extend(
149            self.optional_packages()
150                .map(OptionalPackage::package_declaration),
151        );
152        // Codegen emits imports in this order, so sorting keeps builds byte-for-byte stable.
153        declarations.sort_by(|a, b| a.package_name.cmp(&b.package_name));
154        declarations
155    }
156
157    pub fn install_host_functions(self, linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
158        install_core_host_functions(linker)?;
159        for package in self.optional_packages() {
160            package.install(linker)?;
161        }
162        Ok(())
163    }
164}
165
166/// Why `module` cannot be imported though it names a standard-library package,
167/// as a message and its help lines. `None` when `module` is no such package.
168///
169/// Reached only for a package missing from the declarations the compile was
170/// given, so an optional package here is one the embedder did not enable.
171pub(crate) fn unavailable_import(module: &str) -> Option<(String, Vec<String>)> {
172    if module == test::MODULE_NAME {
173        return Some((
174            format!("package `{module}` not found"),
175            vec![
176                "`submilli:test` is only available to test files run via `submilli build test`; it is not importable from a program run with `submilli run`"
177                    .to_string(),
178            ],
179        ));
180    }
181    let package = OptionalPackage::from_module_name(module)?;
182    Some((
183        format!("package `{module}` is not available in this harness"),
184        vec![format!(
185            "`{}` exists only where the harness running the program provides it",
186            package.module_name()
187        )],
188    ))
189}
190
191/// The core packages: what [`Stdlib::core`] offers.
192pub fn stdlib_package_declarations() -> Vec<PackageDeclaration> {
193    Stdlib::core().package_declarations()
194}
195
196pub fn install_host_functions(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
197    Stdlib::core().install_host_functions(linker)
198}
199
200fn core_package_declarations() -> Vec<PackageDeclaration> {
201    vec![
202        // Alphabetical by package name; codegen import-emission relies on this order.
203        code::package_declaration(),
204        crypto::package_declaration(),
205        embedding::package_declaration(),
206        fs::package_declaration(),
207        git::package_declaration(),
208        http::package_declaration(),
209        llm::package_declaration(),
210        secrets::package_declaration(),
211        security::package_declaration(),
212        session::package_declaration(),
213        url::package_declaration(),
214        uuid::package_declaration(),
215        variables::package_declaration(),
216    ]
217}
218
219fn install_core_host_functions(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
220    code::install(linker)?;
221    crypto::install(linker)?;
222    embedding::install(linker)?;
223    fs::install(linker)?;
224    git::install(linker)?;
225    http::install(linker)?;
226    llm::install(linker)?;
227    secrets::install(linker)?;
228    security::install(linker)?;
229    session::install(linker)?;
230    url::install(linker)?;
231    uuid::install(linker)?;
232    variables::install(linker)?;
233    Ok(())
234}
235
236/// Every host-backed interface of every package, optional ones included.
237pub(crate) fn host_views() -> impl Iterator<Item = &'static abi::HostView> {
238    [
239        agents::HOST_VIEWS,
240        embedding::HOST_VIEWS,
241        fs::HOST_VIEWS,
242        http::HOST_VIEWS,
243        session::HOST_VIEWS,
244        skills::HOST_VIEWS,
245        url::HOST_VIEWS,
246    ]
247    .into_iter()
248    .flatten()
249    .copied()
250}
251
252/// Initialize host-backed standard-library classes for this store.
253pub(crate) fn install_store_bound(
254    linker: &mut Linker<StoreData>,
255    store: &mut wasmtime::Store<StoreData>,
256) -> wasmtime::Result<()> {
257    git::class::install(linker, store)
258}
259
260#[cfg(test)]
261mod optional_tests;
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    /// Every runtime-generic host function is one the typechecker gates: it
268    /// records the schema a schema-checked call's descriptor carries and the
269    /// shapes behind its TypeInfo ids, and rejects a `T` the host's predicate
270    /// cannot test. A host generic the typechecker did not learn would reach
271    /// the host with a descriptor carrying no schema, a fatal host error.
272    #[test]
273    fn every_runtime_generic_host_function_is_a_checked_call() {
274        let all = Stdlib::core()
275            .with(OptionalPackage::Agents)
276            .with(OptionalPackage::Skills);
277        let mut seen = Vec::new();
278        for defs in all.package_declarations() {
279            for mangled in &defs.runtime_generics {
280                assert!(
281                    session::declaration::is_checked_get(mangled)
282                        || json_schema::schema_checked_call(mangled).is_some(),
283                    "{} is runtime-generic but the typechecker does not gate it",
284                    mangled.as_str()
285                );
286                seen.push(mangled.as_str().to_string());
287            }
288        }
289        seen.sort();
290        assert_eq!(
291            seen,
292            [
293                "submilli:agents#run",
294                "submilli:llm#batch",
295                "submilli:llm#call",
296                "submilli:session#get",
297            ],
298            "the host functions that read their type descriptors"
299        );
300    }
301
302    /// Every `Direct` interface with properties is a host-backed value whose
303    /// getters and `JSON.stringify` both read its view, so each needs one
304    /// listing exactly its declared properties.
305    #[test]
306    fn every_host_backed_interface_has_a_view_of_its_properties() {
307        let mut declared = std::collections::BTreeMap::new();
308        let all = Stdlib::core()
309            .with(OptionalPackage::Agents)
310            .with(OptionalPackage::Skills);
311        for defs in all.package_declarations() {
312            for (name, symbol) in &defs.types {
313                if let crate::TypeKind::Interface {
314                    properties,
315                    dispatch: crate::Dispatch::Direct,
316                    ..
317                } = &symbol.kind
318                    && !properties.is_empty()
319                {
320                    let names: std::collections::BTreeSet<String> =
321                        properties.keys().cloned().collect();
322                    declared.insert(
323                        (defs.package_name.clone(), name.as_str().to_string()),
324                        names,
325                    );
326                }
327            }
328        }
329        let mut viewed = std::collections::BTreeMap::new();
330        for view in host_views() {
331            let names: std::collections::BTreeSet<String> = view
332                .properties
333                .iter()
334                .map(|(name, _)| (*name).to_string())
335                .collect();
336            assert_eq!(
337                names.len(),
338                view.properties.len(),
339                "{} repeats a property",
340                view.interface
341            );
342            viewed.insert((view.module.to_string(), view.interface.to_string()), names);
343        }
344        assert_eq!(viewed, declared);
345    }
346
347    /// Host-backed values are told apart by their backing struct, so two
348    /// interfaces whose layouts canonicalize to one type must show the same
349    /// properties.
350    #[test]
351    fn views_sharing_a_backing_struct_show_the_same_properties() {
352        let engine = crate::RuntimeConfig::default().engine().unwrap();
353        let mut seen: Vec<(wasmtime::StructType, &abi::HostView)> = Vec::new();
354        for view in host_views() {
355            let backing = (view.backing)(&engine).unwrap();
356            for (other, earlier) in &seen {
357                if *other == backing {
358                    assert_eq!(
359                        earlier.properties, view.properties,
360                        "{} and {} share a layout",
361                        earlier.interface, view.interface
362                    );
363                }
364            }
365            seen.push((backing, view));
366        }
367    }
368
369    /// Every subset of the optional packages.
370    fn every_set() -> Vec<Stdlib> {
371        let mut sets = vec![Stdlib::core()];
372        for package in OptionalPackage::ALL {
373            let with: Vec<Stdlib> = sets.iter().map(|set| set.with(*package)).collect();
374            sets.extend(with);
375        }
376        sets
377    }
378
379    #[test]
380    fn declarations_are_sorted_for_every_set() {
381        for set in every_set() {
382            let names: Vec<String> = set
383                .package_declarations()
384                .into_iter()
385                .map(|defs| defs.package_name)
386                .collect();
387            let mut sorted = names.clone();
388            sorted.sort();
389            assert_eq!(names, sorted, "{set:?}");
390        }
391    }
392
393    #[test]
394    fn a_set_contains_exactly_its_packages() {
395        for set in every_set() {
396            let names: Vec<String> = set
397                .package_declarations()
398                .into_iter()
399                .map(|defs| defs.package_name)
400                .collect();
401            for name in &names {
402                assert!(set.contains_module(name), "{set:?} misses {name}");
403            }
404            for package in OptionalPackage::ALL {
405                assert_eq!(
406                    names.iter().any(|name| name == package.module_name()),
407                    set.enables(*package),
408                    "{set:?} and {package:?}"
409                );
410            }
411        }
412    }
413
414    #[test]
415    fn core_is_the_global_list() {
416        let core: Vec<String> = Stdlib::core()
417            .package_declarations()
418            .into_iter()
419            .map(|defs| defs.package_name)
420            .collect();
421        let global: Vec<String> = stdlib_package_declarations()
422            .into_iter()
423            .map(|defs| defs.package_name)
424            .collect();
425        assert_eq!(core, global);
426        assert!(!Stdlib::core().contains_module(test::MODULE_NAME));
427    }
428}