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 mod llm;
22pub mod secrets;
23pub mod security;
24pub mod session;
25pub mod shared;
26pub mod skills;
27/// Test-authoring package. Deliberately absent from
28/// [`stdlib_package_declarations`] and [`install_host_functions`]: only
29/// `submilli build test` makes it importable (by passing its declaration into
30/// the compile and installing it directly), so `submilli run` rejects
31/// `import ... from "submilli:test"` as not found.
32pub mod test;
33pub mod url;
34pub mod uuid;
35
36use wasmtime::Linker;
37
38use crate::runtime::StoreData;
39use crate::{MangledName, PackageDeclaration, Type};
40
41/// A standard-library package that exists only when the embedder provides it.
42///
43/// Each one is backed by a provider trait the harness implements. An embedder
44/// that does not enable a package gets none of it: the package cannot be
45/// imported, discovery and the capability catalog do not list it, and its host
46/// functions are not installed.
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
48#[non_exhaustive]
49pub enum OptionalPackage {
50    /// `submilli:agents`, backed by [`AgentProvider`](crate::runtime::AgentProvider).
51    Agents,
52    /// `submilli:skills`, backed by [`SkillProvider`](crate::runtime::SkillProvider).
53    Skills,
54}
55
56impl OptionalPackage {
57    pub const ALL: &'static [OptionalPackage] = &[Self::Agents, Self::Skills];
58
59    pub const fn module_name(self) -> &'static str {
60        match self {
61            Self::Agents => agents::MODULE_NAME,
62            Self::Skills => skills::MODULE_NAME,
63        }
64    }
65
66    pub fn from_module_name(name: &str) -> Option<Self> {
67        Self::ALL
68            .iter()
69            .copied()
70            .find(|package| package.module_name() == name)
71    }
72
73    fn package_declaration(self) -> PackageDeclaration {
74        match self {
75            Self::Agents => agents::package_declaration(),
76            Self::Skills => skills::package_declaration(),
77        }
78    }
79
80    fn install(self, linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
81        match self {
82            Self::Agents => agents::install(linker),
83            Self::Skills => skills::install(linker),
84        }
85    }
86
87    const fn bit(self) -> u8 {
88        match self {
89            Self::Agents => 1,
90            Self::Skills => 1 << 1,
91        }
92    }
93}
94
95/// A host call the typechecker types from its type argument: the compiler emits
96/// a JSON Schema for `T` into the call's trailing `schema` parameter and wraps
97/// the result in a structural check against `T`. What differs between such
98/// calls is what a bare call returns and how diagnostics name it.
99pub(crate) struct SchemaCheckedCall {
100    /// The call as diagnostics name it, e.g. `llm.call`.
101    pub callee: &'static str,
102    /// A typed call to show in a diagnostic.
103    pub example: &'static str,
104    /// What the structural check is applied to.
105    pub checks: &'static str,
106    /// Who the schema is sent to.
107    pub answerer: &'static str,
108    /// The parameters a program writes, in prose; the schema parameter follows.
109    pub written_params: &'static str,
110    pub written_param_count: usize,
111    /// Ends the "cannot be verified" diagnostics: what to do instead of a type
112    /// argument the runtime cannot test.
113    pub untyped_hint: &'static str,
114    /// Ends the `<unknown>` diagnostic.
115    pub untyped_hint_for_unknown: &'static str,
116    /// Ends the "has no JSON Schema" diagnostic.
117    pub untyped_hint_for_no_schema: &'static str,
118    /// What `T` binds to when the program wrote no type argument.
119    pub untyped_result: Type,
120}
121
122impl SchemaCheckedCall {
123    /// The schema parameter's position, as an ordinal word.
124    pub fn schema_argument_ordinal(&self) -> &'static str {
125        match self.written_param_count {
126            0 => "first",
127            1 => "second",
128            2 => "third",
129            3 => "fourth",
130            _ => "last",
131        }
132    }
133}
134
135/// The schema-checked call `mangled` names, if it is one.
136pub(crate) fn schema_checked_call(mangled: &MangledName) -> Option<SchemaCheckedCall> {
137    if llm::declaration::is_checked_call(mangled) {
138        return Some(SchemaCheckedCall {
139            callee: "llm.call",
140            example: "llm.call<Severity>(model, prompt)",
141            checks: "the model's response",
142            answerer: "the model",
143            written_params: "the model and the prompt",
144            written_param_count: 2,
145            untyped_hint: "read the `Completion` envelope yourself",
146            untyped_hint_for_unknown: "read the `Completion` envelope's `ok` and `text` yourself",
147            untyped_hint_for_no_schema: "parse the `Completion` text yourself",
148            untyped_result: llm::declaration::untyped_result_type(mangled),
149        });
150    }
151    if agents::declaration::is_checked_run(mangled) {
152        return Some(SchemaCheckedCall {
153            callee: "agents.run",
154            example: "agents.run<Report>(agent, input)",
155            checks: "the agent's result",
156            answerer: "the agent",
157            written_params: "the agent and the input",
158            written_param_count: 2,
159            untyped_hint: "read the returned text yourself",
160            untyped_hint_for_unknown: "read the returned text yourself",
161            untyped_hint_for_no_schema: "parse the returned text yourself",
162            untyped_result: Type::String,
163        });
164    }
165    None
166}
167
168/// The standard library an embedder offers its programs: the core packages
169/// every embedder has, plus the [`OptionalPackage`]s it enables.
170///
171/// Compiling, discovery, the capability catalog and host-function installation
172/// all read the same set, so a package is either fully present or fully absent.
173/// The discovery methods (`docs`, `search`, `resolve`, …) are in
174/// [`crate::packages`].
175#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
176pub struct Stdlib {
177    optional: u8,
178}
179
180impl Stdlib {
181    /// The core packages only, what `submilli run` and the server offer.
182    pub const fn core() -> Self {
183        Stdlib { optional: 0 }
184    }
185
186    #[must_use]
187    pub const fn with(self, package: OptionalPackage) -> Self {
188        Stdlib {
189            optional: self.optional | package.bit(),
190        }
191    }
192
193    pub const fn enables(self, package: OptionalPackage) -> bool {
194        self.optional & package.bit() != 0
195    }
196
197    pub fn optional_packages(self) -> impl Iterator<Item = OptionalPackage> {
198        OptionalPackage::ALL
199            .iter()
200            .copied()
201            .filter(move |package| self.enables(*package))
202    }
203
204    /// Whether `name` is a package of this set.
205    pub fn contains_module(self, name: &str) -> bool {
206        OptionalPackage::from_module_name(name).map_or_else(
207            || {
208                core_package_declarations()
209                    .iter()
210                    .any(|defs| defs.package_name == name)
211            },
212            |package| self.enables(package),
213        )
214    }
215
216    /// Every package of this set, sorted by name.
217    pub fn package_declarations(self) -> Vec<PackageDeclaration> {
218        let mut declarations = core_package_declarations();
219        declarations.extend(
220            self.optional_packages()
221                .map(OptionalPackage::package_declaration),
222        );
223        // Codegen emits imports in this order, so sorting keeps builds byte-for-byte stable.
224        declarations.sort_by(|a, b| a.package_name.cmp(&b.package_name));
225        declarations
226    }
227
228    pub fn install_host_functions(self, linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
229        install_core_host_functions(linker)?;
230        for package in self.optional_packages() {
231            package.install(linker)?;
232        }
233        Ok(())
234    }
235}
236
237/// Why `module` cannot be imported though it names a standard-library package,
238/// as a message and its help lines. `None` when `module` is no such package.
239///
240/// Reached only for a package missing from the declarations the compile was
241/// given, so an optional package here is one the embedder did not enable.
242pub(crate) fn unavailable_import(module: &str) -> Option<(String, Vec<String>)> {
243    if module == test::MODULE_NAME {
244        return Some((
245            format!("package `{module}` not found"),
246            vec![
247                "`submilli:test` is only available to test files run via `submilli build test`; it is not importable from a program run with `submilli run`"
248                    .to_string(),
249            ],
250        ));
251    }
252    let package = OptionalPackage::from_module_name(module)?;
253    Some((
254        format!("package `{module}` is not available in this harness"),
255        vec![format!(
256            "`{}` exists only where the harness running the program provides it",
257            package.module_name()
258        )],
259    ))
260}
261
262/// The core packages: what [`Stdlib::core`] offers.
263pub fn stdlib_package_declarations() -> Vec<PackageDeclaration> {
264    Stdlib::core().package_declarations()
265}
266
267pub fn install_host_functions(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
268    Stdlib::core().install_host_functions(linker)
269}
270
271fn core_package_declarations() -> Vec<PackageDeclaration> {
272    vec![
273        // Alphabetical by package name; codegen import-emission relies on this order.
274        code::package_declaration(),
275        crypto::package_declaration(),
276        embedding::package_declaration(),
277        fs::package_declaration(),
278        git::package_declaration(),
279        http::package_declaration(),
280        llm::package_declaration(),
281        secrets::package_declaration(),
282        security::package_declaration(),
283        session::package_declaration(),
284        url::package_declaration(),
285        uuid::package_declaration(),
286    ]
287}
288
289fn install_core_host_functions(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
290    code::install(linker)?;
291    crypto::install(linker)?;
292    embedding::install(linker)?;
293    fs::install(linker)?;
294    git::install(linker)?;
295    http::install(linker)?;
296    llm::install(linker)?;
297    secrets::install(linker)?;
298    security::install(linker)?;
299    session::install(linker)?;
300    url::install(linker)?;
301    uuid::install(linker)?;
302    Ok(())
303}
304
305/// Initialize host-backed standard-library classes for this store.
306pub(crate) fn install_store_bound(
307    linker: &mut Linker<StoreData>,
308    store: &mut wasmtime::Store<StoreData>,
309) -> wasmtime::Result<()> {
310    git::class::install(linker, store)
311}
312
313#[cfg(test)]
314mod optional_tests;
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    /// Every subset of the optional packages.
321    fn every_set() -> Vec<Stdlib> {
322        let mut sets = vec![Stdlib::core()];
323        for package in OptionalPackage::ALL {
324            let with: Vec<Stdlib> = sets.iter().map(|set| set.with(*package)).collect();
325            sets.extend(with);
326        }
327        sets
328    }
329
330    #[test]
331    fn declarations_are_sorted_for_every_set() {
332        for set in every_set() {
333            let names: Vec<String> = set
334                .package_declarations()
335                .into_iter()
336                .map(|defs| defs.package_name)
337                .collect();
338            let mut sorted = names.clone();
339            sorted.sort();
340            assert_eq!(names, sorted, "{set:?}");
341        }
342    }
343
344    #[test]
345    fn a_set_contains_exactly_its_packages() {
346        for set in every_set() {
347            let names: Vec<String> = set
348                .package_declarations()
349                .into_iter()
350                .map(|defs| defs.package_name)
351                .collect();
352            for name in &names {
353                assert!(set.contains_module(name), "{set:?} misses {name}");
354            }
355            for package in OptionalPackage::ALL {
356                assert_eq!(
357                    names.iter().any(|name| name == package.module_name()),
358                    set.enables(*package),
359                    "{set:?} and {package:?}"
360                );
361            }
362        }
363    }
364
365    #[test]
366    fn core_is_the_global_list() {
367        let core: Vec<String> = Stdlib::core()
368            .package_declarations()
369            .into_iter()
370            .map(|defs| defs.package_name)
371            .collect();
372        let global: Vec<String> = stdlib_package_declarations()
373            .into_iter()
374            .map(|defs| defs.package_name)
375            .collect();
376        assert_eq!(core, global);
377        assert!(!Stdlib::core().contains_module(test::MODULE_NAME));
378    }
379}