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        embedding::HOST_VIEWS,
241        http::HOST_VIEWS,
242        skills::HOST_VIEWS,
243        url::HOST_VIEWS,
244    ]
245    .into_iter()
246    .flatten()
247    .copied()
248}
249
250/// Initialize host-backed standard-library classes for this store.
251pub(crate) fn install_store_bound(
252    linker: &mut Linker<StoreData>,
253    store: &mut wasmtime::Store<StoreData>,
254) -> wasmtime::Result<()> {
255    git::class::install(linker, store)
256}
257
258#[cfg(test)]
259mod optional_tests;
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    /// Every runtime-generic host function or method is a row of the
266    /// checked-call table the typechecker gates by: the gate rejects a `T`
267    /// the host's predicate cannot test, or a schema call's `T` with no
268    /// schema. A host generic outside the table would reach the host at a
269    /// type nothing vetted.
270    #[test]
271    fn every_runtime_generic_host_function_is_a_checked_call() {
272        let all = Stdlib::core()
273            .with(OptionalPackage::Agents)
274            .with(OptionalPackage::Skills);
275        let mut seen = Vec::new();
276        for defs in all.package_declarations() {
277            for mangled in &defs.runtime_generics {
278                assert!(
279                    checked_call::checked_call(mangled).is_some(),
280                    "{} is runtime-generic but the typechecker does not gate it",
281                    mangled.as_str()
282                );
283                seen.push(mangled.as_str().to_string());
284            }
285        }
286        seen.sort();
287        assert_eq!(
288            seen,
289            [
290                "submilli:agents#run",
291                "submilli:http#Response#json",
292                "submilli:llm#batch",
293                "submilli:llm#call",
294                "submilli:session#get",
295            ],
296            "the host functions that read their type descriptors"
297        );
298    }
299
300    /// Every `Direct` interface with properties is a host-backed value whose
301    /// getters and `JSON.stringify` both read its view, so each needs one
302    /// listing exactly its declared properties.
303    #[test]
304    fn every_host_backed_interface_has_a_view_of_its_properties() {
305        let mut declared = std::collections::BTreeMap::new();
306        let all = Stdlib::core()
307            .with(OptionalPackage::Agents)
308            .with(OptionalPackage::Skills);
309        for defs in all.package_declarations() {
310            for (name, symbol) in &defs.types {
311                if let crate::TypeKind::Interface {
312                    properties,
313                    dispatch: crate::Dispatch::Direct,
314                    ..
315                } = &symbol.kind
316                    && !properties.is_empty()
317                {
318                    let names: std::collections::BTreeSet<String> =
319                        properties.keys().cloned().collect();
320                    declared.insert(
321                        (defs.package_name.clone(), name.as_str().to_string()),
322                        names,
323                    );
324                }
325            }
326        }
327        let mut viewed = std::collections::BTreeMap::new();
328        for view in host_views() {
329            let names: std::collections::BTreeSet<String> = view
330                .properties
331                .iter()
332                .map(|(name, _)| (*name).to_string())
333                .collect();
334            assert_eq!(
335                names.len(),
336                view.properties.len(),
337                "{} repeats a property",
338                view.interface
339            );
340            viewed.insert((view.module.to_string(), view.interface.to_string()), names);
341        }
342        assert_eq!(viewed, declared);
343    }
344
345    /// Host-backed values are told apart by their backing struct, so two
346    /// interfaces whose layouts canonicalize to one type must show the same
347    /// properties.
348    #[test]
349    fn views_sharing_a_backing_struct_show_the_same_properties() {
350        let engine = crate::RuntimeConfig::default().engine().unwrap();
351        let mut seen: Vec<(wasmtime::StructType, &abi::HostView)> = Vec::new();
352        for view in host_views() {
353            let backing = (view.backing)(&engine).unwrap();
354            for (other, earlier) in &seen {
355                if *other == backing {
356                    assert_eq!(
357                        earlier.properties, view.properties,
358                        "{} and {} share a layout",
359                        earlier.interface, view.interface
360                    );
361                }
362            }
363            seen.push((backing, view));
364        }
365    }
366
367    /// Every subset of the optional packages.
368    fn every_set() -> Vec<Stdlib> {
369        let mut sets = vec![Stdlib::core()];
370        for package in OptionalPackage::ALL {
371            let with: Vec<Stdlib> = sets.iter().map(|set| set.with(*package)).collect();
372            sets.extend(with);
373        }
374        sets
375    }
376
377    #[test]
378    fn declarations_are_sorted_for_every_set() {
379        for set in every_set() {
380            let names: Vec<String> = set
381                .package_declarations()
382                .into_iter()
383                .map(|defs| defs.package_name)
384                .collect();
385            let mut sorted = names.clone();
386            sorted.sort();
387            assert_eq!(names, sorted, "{set:?}");
388        }
389    }
390
391    #[test]
392    fn a_set_contains_exactly_its_packages() {
393        for set in every_set() {
394            let names: Vec<String> = set
395                .package_declarations()
396                .into_iter()
397                .map(|defs| defs.package_name)
398                .collect();
399            for name in &names {
400                assert!(set.contains_module(name), "{set:?} misses {name}");
401            }
402            for package in OptionalPackage::ALL {
403                assert_eq!(
404                    names.iter().any(|name| name == package.module_name()),
405                    set.enables(*package),
406                    "{set:?} and {package:?}"
407                );
408            }
409        }
410    }
411
412    #[test]
413    fn core_is_the_global_list() {
414        let core: Vec<String> = Stdlib::core()
415            .package_declarations()
416            .into_iter()
417            .map(|defs| defs.package_name)
418            .collect();
419        let global: Vec<String> = stdlib_package_declarations()
420            .into_iter()
421            .map(|defs| defs.package_name)
422            .collect();
423        assert_eq!(core, global);
424        assert!(!Stdlib::core().contains_module(test::MODULE_NAME));
425    }
426}