1pub mod agents;
4pub(crate) mod array_storage;
5pub mod blocking;
6pub mod call_log;
7pub mod decision;
8pub mod default_filesystem;
9pub mod disk_quota;
10pub mod durable;
11pub mod embedding;
12pub mod exec;
13pub mod filesystem;
14pub mod fs;
15pub mod fuel;
16pub mod gc_singleton;
17pub mod host;
18pub mod intrinsic_types;
19pub mod json;
20pub(crate) mod json_text;
21pub mod limits;
22pub mod llm;
23pub mod mcp;
24pub mod metrics;
25pub mod number;
26pub mod prelude;
27pub mod secrets;
28pub mod security;
29pub mod session_finalizers;
30pub mod session_kv;
31pub mod skills;
32pub mod token_ledger;
33pub mod tools;
34pub mod vfs;
35pub mod watchdog;
36
37pub use agents::{
38 AGENTS_MODULE_NAME, AgentCallError, AgentInfo, AgentOutcome, AgentProvider, AgentRequest,
39 AgentUsage,
40};
41pub use call_log::{BodyCopy, CallOutcome, CallRecord, ModelUsage, PayloadRecord};
42pub use decision::{
43 CallOrigin, CallSite, CallTicket, ConsoleLine, DecisionAction, DecisionCause,
44 DecisionExplanation, DecisionLog, DecisionLogConfig, DecisionLogOutput, DecisionRecord,
45 DecisionRecorder, EntryPath, FailureReasonRecord, FailureRecord, NearMissRecord, PackageLine,
46 RecordObserver, RuleCitation, SourceLine,
47};
48pub use disk_quota::{DiskQuota, Holder, OpenFileGuard, QuotaCharge, QuotaExceeded};
49pub use embedding::{
50 DEFAULT_MAX_EMBEDDING_REQUESTS, DEFAULT_MAX_EXECUTION_EMBEDDING_TOKENS, EMBEDDING_MODULE_NAME,
51 EmbeddingBatch, EmbeddingBoundKind, EmbeddingError, EmbeddingFailureReason, EmbeddingLimitKind,
52 EmbeddingLimits, EmbeddingMalformedReason, EmbeddingModel, EmbeddingProvider,
53 EmbeddingShapeError, EmbeddingTokenBudget, Purpose, SubBatchSettlement,
54 estimate_embedding_tokens,
55};
56pub use exec::{RunResult, dispatch_main_async, instantiate_program_async};
57pub use host::{
58 INTERNAL_MODULE_NAME, NUMBER_MODULE_NAME, host_package_declarations,
59 install_async as install_runtime_async, install_async_for as install_runtime_async_for,
60 install_host_functions as install_runtime_host_functions,
61 install_host_functions_for as install_runtime_host_functions_for,
62 install_store_bound as install_runtime_store_bound, internal_host_package_declarations,
63 stdlib_package_declarations,
64};
65pub use json::JSON_MODULE_NAME;
66pub use limits::{
67 DEFAULT_MAX_STORE_BYTES, MemoryCapExceeded, MemoryExhausted, TenantLimits,
68 install_tenant_limits, is_memory_exhausted,
69};
70pub use llm::{
71 DEFAULT_MAX_ALL_EXECUTIONS_TOKENS, DEFAULT_MAX_EXECUTION_TOKENS, ExecutionTokenBudget,
72 FailureReason, LLM_MODULE_NAME, LlmCallError, LlmFailure, LlmLimitKind, LlmLimits, LlmModel,
73 LlmOutcome, LlmProvider, PromptBoundKind, SharedTokenBudget,
74};
75pub use mcp::{
76 MCP_MODULE_NAME, McpCallError, McpOutcome, McpResponse, McpTransport, install_mcp_async,
77 mcp_call_package_declaration,
78};
79pub use metrics::{HttpMetric, MetricsSink, NoopMetricsSink};
80pub use prelude::bigint::ops::BIGINT_MODULE_NAME;
81pub use prelude::temporal::shared::TEMPORAL_MODULE_NAME;
82pub use secrets::{NoopSecretProvider, SecretProvider};
83pub use security::{AllowAllCheck, AuditDecision, CheckOutcome, SecurityCheck};
84pub use session_kv::{
85 CURSOR_KEY_ID_LEN, CursorKey, CursorKeys, InMemorySessionKv, SessionKvEntry, SessionKvError,
86 SessionKvFuture, SessionKvLimitKind, SessionKvLimits, SessionKvPage, SessionKvStore,
87 SessionKvUsage, entry_bytes, render_key, value_bytes,
88};
89pub use skills::{SKILLS_MODULE_NAME, Skill, SkillError, SkillInfo, SkillProvider};
90pub use tools::{
91 TOOLS_CAPABILITY, TOOLS_MODULE_NAME, TOOLS_PACKAGE, ToolCallError, ToolProvider,
92 install_tools_async, tools_call_package_declaration,
93};
94pub use vfs::{
95 Access, CopyDirError, MAX_MEASURED_DEPTH, MountError, MountSpec, Vfs, VfsMode, copy_host_dir,
96 copy_host_subdir, measure_dir, measure_host_dir, measure_host_dir_skipping_vanished,
97 measure_host_subdir_skipping_vanished, open_host_subdir, regular_files,
98};
99pub use watchdog::Watchdog;
100
101pub use crate::stdlib::http::{
102 AuthProxy, AuthProxyError, HttpClient, HttpError, HttpRequest, HttpResponse, NetworkPolicy,
103 NoopAuthProxy, ReqwestHttpClient,
104};
105
106use std::cell::RefCell;
107use std::io::Write;
108use std::sync::{Arc, Mutex};
109use std::time::Duration;
110
111use wasmtime::{
112 ArrayRef, AsContextMut, Config, Engine, Instance, Linker, Module, OptLevel, Rooted, Store,
113 WasmBacktraceDetails,
114};
115
116use crate::{PackageDeclaration, TypeInfoTable};
117
118#[derive(Debug, Clone)]
122pub struct VfsInfo {
123 pub mode: VfsMode,
124 pub size_limit: Option<u64>,
125}
126
127pub struct StoreData {
128 #[cfg(test)]
129 pub(crate) array_growth: array_storage::GrowthStats,
130 pub blocking_work: blocking::BlockingWork,
133 pub git: Option<crate::stdlib::git::GitConfig>,
134 pub(crate) git_history: Option<Arc<crate::stdlib::git::log_cache::Cache>>,
135 pub console: Box<dyn Write + Send>,
136 pub vfs: Vfs,
137 pub filesystem: Arc<dyn filesystem::Filesystem>,
138 pub vfs_info: VfsInfo,
139 pub security_check: Arc<dyn SecurityCheck>,
140 pub fs_max_read_size: u64,
141 pub http_client: Arc<dyn HttpClient>,
142 pub http_max_response_size: u64,
143 pub http_max_download_timeout_ms: u64,
145 pub auth_proxy: Arc<dyn AuthProxy>,
146 pub variables: Arc<std::collections::BTreeMap<String, String>>,
148 pub secret_provider: Arc<dyn SecretProvider>,
149 pub mcp_transport: Option<Arc<dyn McpTransport>>,
153 pub tool_provider: Option<Arc<dyn ToolProvider>>,
157 pub clock_paused: std::time::Duration,
161 pub llm_provider: Option<Arc<dyn LlmProvider>>,
167 pub llm_budget: Option<Arc<ExecutionTokenBudget>>,
174 pub embedding_provider: Option<Arc<dyn EmbeddingProvider>>,
178 pub embedding_budget: Option<Arc<EmbeddingTokenBudget>>,
181 pub session_kv: Option<Arc<dyn SessionKvStore>>,
186 pub session_finalizers: Option<Arc<dyn session_finalizers::SessionFinalizers>>,
188 pub finalizing: bool,
189 pub cursor_keys: Option<Arc<CursorKeys>>,
192 pub agent_provider: Option<Arc<dyn AgentProvider>>,
196 pub skill_provider: Option<Arc<dyn SkillProvider>>,
200 pub metrics: Arc<dyn metrics::MetricsSink>,
203 pub tenant_limits: TenantLimits,
204 pub host_fuel: u64,
207 pub(crate) next_identity_hash: u64,
208 #[cfg(test)]
209 pub(crate) collection_index_reads: u64,
210 #[cfg(test)]
211 pub(crate) object_index_probes: u64,
212 pub host_fuel_pending: u64,
215 pub host_fuel_applied_at: Option<u64>,
218 pub test_labels: RefCell<Vec<String>>,
223 pub host_abi: Option<crate::runtime::host::HostAbi>,
228 pub(crate) intrinsic_types: Option<Arc<crate::runtime::intrinsic_types::IntrinsicTypes>>,
231 pub(crate) undefined_value: Option<wasmtime::Global>,
233 pub(crate) closure_receiver_type: Option<wasmtime::StructType>,
236 pub(crate) call_metadata_type: Option<wasmtime::StructType>,
239 pub(crate) iterator_functions: [Option<wasmtime::Func>; 13],
240 pub(crate) iterator_constants: [Option<wasmtime::Global>; 6],
241 pub(crate) static_values: std::collections::BTreeMap<Vec<u16>, wasmtime::Global>,
244 pub(crate) parameter_cache: prelude::arguments::ParameterCache,
245 pub(crate) regex_input: Option<prelude::regex::input::InputCache>,
246 pub type_info: std::collections::BTreeMap<String, TypeInfoTable>,
248 pub vtable_walk_depth: u32,
251 pub(crate) vtable_walk_nodes: u32,
254 pub(crate) settling_host_result: bool,
256 pub(crate) thrown_denials: host::ThrownDenials,
258}
259
260pub(crate) const MAX_VTABLE_WALK_DEPTH: u32 = 128;
269
270pub(crate) const MAX_STRUCTURAL_WALK_NODES: u32 = 100_000;
272
273pub const DEFAULT_FS_MAX_READ_SIZE: u64 = 50 * 1024 * 1024;
274
275pub const DEFAULT_HTTP_MAX_RESPONSE_SIZE: u64 = 50 * 1024 * 1024;
276
277#[derive(Clone, Copy)]
278pub struct LinkedPackageModule<'a> {
279 pub module: &'a Module,
280 pub declaration: &'a PackageDeclaration,
281 pub type_info: &'a TypeInfoTable,
282}
283
284impl StoreData {
285 pub fn with_vfs(vfs: Vfs) -> Self {
286 Self::with_vfs_and_cap(vfs, DEFAULT_MAX_STORE_BYTES)
287 }
288
289 pub fn with_vfs_and_cap(vfs: Vfs, max_store_bytes: u64) -> Self {
290 let vfs_info = VfsInfo {
291 mode: vfs.mode(),
292 size_limit: None,
293 };
294 Self {
295 #[cfg(test)]
296 array_growth: array_storage::GrowthStats::default(),
297 console: Box::new(std::io::stderr()),
298 filesystem: Arc::new(default_filesystem::DefaultFilesystem::new(vfs.clone())),
299 vfs,
300 vfs_info,
301 security_check: security::default_check(),
302 git: None,
303 git_history: None,
304 blocking_work: blocking::BlockingWork::default(),
305 fs_max_read_size: DEFAULT_FS_MAX_READ_SIZE,
306 http_client: crate::stdlib::http::default_http_client(),
307 http_max_response_size: DEFAULT_HTTP_MAX_RESPONSE_SIZE,
308 http_max_download_timeout_ms: 60_000,
309 auth_proxy: crate::stdlib::http::default_auth_proxy(),
310 variables: Arc::default(),
311 secret_provider: Arc::new(secrets::NoopSecretProvider),
312 mcp_transport: None,
313 tool_provider: None,
314 clock_paused: std::time::Duration::ZERO,
315 llm_provider: None,
316 llm_budget: None,
317 embedding_provider: None,
318 embedding_budget: None,
319 session_kv: None,
320 session_finalizers: None,
321 finalizing: false,
322 cursor_keys: None,
323 agent_provider: None,
324 skill_provider: None,
325 metrics: Arc::new(metrics::NoopMetricsSink),
326 tenant_limits: TenantLimits::new(max_store_bytes),
327 host_fuel: 0,
328 #[cfg(test)]
329 collection_index_reads: 0,
330 #[cfg(test)]
331 object_index_probes: 0,
332 next_identity_hash: 0,
333 host_fuel_pending: 0,
334 host_fuel_applied_at: None,
335 test_labels: RefCell::new(Vec::new()),
336 host_abi: None,
337 intrinsic_types: None,
338 undefined_value: None,
339 closure_receiver_type: None,
340 call_metadata_type: None,
341 iterator_functions: [None; 13],
342 iterator_constants: [None; 6],
343 static_values: std::collections::BTreeMap::new(),
344 parameter_cache: Default::default(),
345 regex_input: None,
346 type_info: std::collections::BTreeMap::new(),
347 vtable_walk_depth: 0,
348 vtable_walk_nodes: 0,
349 settling_host_result: false,
350 thrown_denials: host::ThrownDenials::default(),
351 }
352 }
353
354 pub fn install_type_info(&mut self, table: TypeInfoTable) {
355 self.type_info.insert(table.package_name.clone(), table);
356 }
357
358 pub fn with_tempdir() -> std::io::Result<Self> {
359 Ok(Self::with_vfs(Vfs::tempdir()?))
360 }
361}
362
363pub async fn install_package_modules_async(
368 linker: &mut Linker<StoreData>,
369 store: &mut Store<StoreData>,
370 packages: &[LinkedPackageModule<'_>],
371) -> wasmtime::Result<()> {
372 for package in packages {
373 let name = package.declaration.package_name.as_str();
374 if store.data().git.is_none()
375 && package
376 .module
377 .imports()
378 .any(|import| import.module() == "submilli:git")
379 {
380 wasmtime::bail!(
381 "package `{name}` imports submilli:git; configure the blueprint git block"
382 );
383 }
384 match package.module.name() {
391 Some(declared) if declared == name => {}
392 Some(declared) => wasmtime::bail!(
393 "package `{name}`: its module declares the name `{declared}`, which would give \
394 it that principal's permissions. Rebuild the package."
395 ),
396 None => wasmtime::bail!(
397 "package `{name}`: its module declares no name, so its gated calls cannot be \
398 attributed. Rebuild the package."
399 ),
400 }
401 store
402 .data_mut()
403 .install_type_info(package.type_info.clone());
404 let instance = {
408 let outcome = linker.instantiate_async(&mut *store, package.module).await;
409 name_the_failing_initializer(&mut *store, name, outcome)?
410 };
411 linker.instance(&mut *store, name, instance)?;
412 }
413 Ok(())
414}
415
416fn name_the_failing_initializer(
425 store: &mut Store<StoreData>,
426 package: &str,
427 outcome: wasmtime::Result<Instance>,
428) -> wasmtime::Result<Instance> {
429 let err = match outcome {
430 Ok(instance) => return Ok(instance),
431 Err(err) => err,
432 };
433 let recovered = exec::uncaught_error(store, err);
434 let Some(thrown) = recovered.downcast_ref::<crate::backtrace::ThrownError>() else {
435 return Err(recovered);
436 };
437 Err(wasmtime::Error::new(crate::backtrace::ThrownError {
438 message: format!(
439 "package `{package}` failed to initialize: {}",
440 thrown.message
441 ),
442 backtrace: thrown.backtrace.clone(),
443 denial: thrown.denial.clone(),
444 }))
445}
446
447const NATIVE_STACK_PER_WASM_BYTE: usize = 32;
450const MIN_NATIVE_STACK: usize = 8 * 1024 * 1024;
451
452#[derive(Clone, Debug)]
453pub struct RuntimeConfig {
454 pub fuel: u64,
455 pub max_wasm_stack: usize,
456 pub memory_reservation: u64,
457 pub memory_guard_size: u64,
458 pub memory_reservation_for_growth: u64,
459 pub max_store_bytes: u64,
460 pub timeout: Option<Duration>,
461 pub async_yield_fuel: Option<u64>,
462}
463
464impl Default for RuntimeConfig {
465 fn default() -> Self {
466 Self {
467 fuel: 1_000_000_000_000,
473 max_wasm_stack: 512 * 1024,
474 memory_reservation: 3 * 1024 * 1024,
475 memory_guard_size: 64 * 1024,
476 memory_reservation_for_growth: 16 * 1024 * 1024,
477 max_store_bytes: DEFAULT_MAX_STORE_BYTES,
478 timeout: None,
479 async_yield_fuel: Some(10_000),
484 }
485 }
486}
487
488impl RuntimeConfig {
489 pub fn native_stack_size(&self) -> usize {
511 self.max_wasm_stack
512 .saturating_mul(NATIVE_STACK_PER_WASM_BYTE)
513 .max(MIN_NATIVE_STACK)
514 }
515
516 pub fn engine(&self) -> wasmtime::Result<Engine> {
517 Engine::new(&self.wasmtime_config())
518 }
519
520 pub fn engine_async(&self) -> wasmtime::Result<Engine> {
524 Engine::new(&self.wasmtime_config())
525 }
526
527 pub fn wasmtime_config(&self) -> Config {
528 let mut config = Config::new();
529 config.cranelift_opt_level(OptLevel::None);
540 config.consume_fuel(true);
541 config.max_wasm_stack(self.max_wasm_stack);
542
543 config.epoch_interruption(true);
544
545 config.memory_reservation(self.memory_reservation);
546 config.memory_guard_size(self.memory_guard_size);
547 config.memory_reservation_for_growth(self.memory_reservation_for_growth);
548 config.memory_may_move(true);
549 config.memory_init_cow(true);
550
551 config.gc_heap_reservation(0);
555 config.gc_heap_guard_size(self.memory_guard_size);
556 config.gc_heap_reservation_for_growth(self.memory_reservation_for_growth);
557 config.gc_heap_may_move(true);
558
559 config.wasm_gc(true);
560 config.wasm_function_references(true);
561 config.wasm_exceptions(true);
562
563 config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
564
565 config
566 }
567
568 pub fn store<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
569 let mut store = Store::new(engine, data);
570 store.set_fuel(self.fuel)?;
571 let delta = if self.timeout.is_some() { 1 } else { u64::MAX };
574 store.set_epoch_deadline(delta);
575 store.epoch_deadline_trap();
576 Ok(store)
577 }
578
579 pub fn store_async<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
580 let mut store = self.store(engine, data)?;
581 store.fuel_async_yield_interval(self.async_yield_fuel)?;
582 Ok(store)
583 }
584
585 pub fn arm_timeout(&self, engine: &Engine) -> wasmtime::Result<Option<Watchdog>> {
588 self.timeout
589 .map(|duration| watchdog::arm(engine, duration))
590 .transpose()
591 .map_err(|error| {
592 wasmtime::Error::from(error).context("starting execution timeout watchdog")
593 })
594 }
595
596 pub async fn run(&self, wasm_bytes: &[u8]) -> wasmtime::Result<RunResult> {
609 self.run_with_type_info(wasm_bytes, None).await
610 }
611
612 pub async fn run_compiled(
620 &self,
621 compiled: &crate::compile::CompiledScript,
622 ) -> wasmtime::Result<RunResult> {
623 self.run_with_type_info(&compiled.wasm, Some(compiled.type_info.clone()))
624 .await
625 }
626
627 async fn run_with_type_info(
628 &self,
629 wasm_bytes: &[u8],
630 type_info: Option<TypeInfoTable>,
631 ) -> wasmtime::Result<RunResult> {
632 let buf = Arc::new(Mutex::new(Vec::new()));
633 let mut data = StoreData::with_vfs_and_cap(Vfs::tempdir()?, self.max_store_bytes);
634 data.console = Box::new(ConsoleSink(Arc::clone(&buf)));
635 if let Some(type_info) = type_info {
636 data.install_type_info(type_info);
637 }
638 let engine = self.engine()?;
639 let mut store = self.store_async(&engine, data)?;
640 install_tenant_limits(&mut store);
641 let module = Module::new(&engine, wasm_bytes)?;
642 let mut linker = Linker::<StoreData>::new(&engine);
643 install_runtime_async(&mut linker, &mut store).await?;
644 let _watchdog = self.arm_timeout(&engine)?;
645 let inst = instantiate_program_async(&linker, &mut store, &module).await?;
646 let value = dispatch_main_async(&mut store, &inst).await?;
647 let captured = buf.lock().expect("console buffer lock poisoned").clone();
651 let console = String::from_utf8(captured)
652 .map_err(|e| wasmtime::Error::msg(format!("console output not utf-8: {e}")))?;
653 Ok(RunResult { value, console })
654 }
655}
656
657struct ConsoleSink(Arc<Mutex<Vec<u8>>>);
661
662impl Write for ConsoleSink {
663 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
664 self.0
665 .lock()
666 .expect("console buffer lock poisoned")
667 .write(buf)
668 }
669
670 fn flush(&mut self) -> std::io::Result<()> {
671 Ok(())
672 }
673}
674
675pub(crate) fn read_submilli_string(
677 mut ctx: impl AsContextMut<Data = StoreData>,
678 msg: Rooted<ArrayRef>,
679) -> wasmtime::Result<String> {
680 let units = host::read_code_units(&mut ctx, msg, "string")?;
681 fuel::charge(ctx, fuel::SCAN, units.len() as u64)?;
682 Ok(String::from_utf16_lossy(&units))
683}