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