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