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