1use crate::runtime::durable::RunId;
4pub mod agents;
5pub mod approvals;
6pub(crate) mod array_storage;
7pub mod blocking;
8pub mod call_log;
9pub mod decision;
10pub mod default_filesystem;
11pub mod disk_quota;
12pub mod durable;
13pub mod effects;
14mod effects_memory;
15pub mod embedding;
16pub mod exec;
17pub mod filesystem;
18pub mod finalizers;
19pub mod fs;
20pub mod fuel;
21pub mod gc_singleton;
22pub mod host;
23pub mod intrinsic_types;
24pub mod json;
25pub(crate) mod json_text;
26pub mod limits;
27pub mod llm;
28pub mod mcp;
29pub mod metrics;
30pub mod model_calls;
31pub mod number;
32pub mod prelude;
33pub mod secrets;
34pub mod security;
35pub mod session_finalizers;
36pub mod session_kv;
37pub mod skills;
38pub mod token_ledger;
39pub mod tools;
40pub mod vfs;
41pub mod watchdog;
42
43pub use agents::{
44 AGENTS_MODULE_NAME, AgentCallError, AgentInfo, AgentOutcome, AgentProvider, AgentRequest,
45 AgentUsage,
46};
47pub use approvals::{
48 APPROVAL_OPERATION, ApprovalError, ApprovalOutcome, ApprovalProvider, ApprovalRequest,
49 Approver, AskRule, Unasked,
50};
51pub use call_log::{BodyCopy, CallOutcome, CallRecord, ModelUsage, PayloadRecord};
52pub use decision::{
53 CallOrigin, CallSite, CallTicket, ConsoleLine, DecisionAction, DecisionCause,
54 DecisionExplanation, DecisionLog, DecisionLogConfig, DecisionLogOutput, DecisionRecord,
55 DecisionRecorder, EntryPath, FailureReasonRecord, FailureRecord, NearMissRecord, PackageLine,
56 RecordObserver, RuleCitation, SourceLine,
57};
58pub use disk_quota::{DiskQuota, Holder, OpenFileGuard, QuotaCharge, QuotaExceeded};
59pub use embedding::{
60 DEFAULT_MAX_EMBEDDING_REQUESTS, DEFAULT_MAX_EXECUTION_EMBEDDING_TOKENS, EMBEDDING_MODULE_NAME,
61 EmbeddingBatch, EmbeddingBoundKind, EmbeddingError, EmbeddingFailureReason, EmbeddingLimitKind,
62 EmbeddingLimits, EmbeddingMalformedReason, EmbeddingModel, EmbeddingProvider,
63 EmbeddingShapeError, EmbeddingTokenBudget, Purpose, SubBatchSettlement,
64 estimate_embedding_tokens,
65};
66pub use exec::{RunResult, dispatch_main_async, instantiate_program_async};
67pub use finalizers::{FinalizerCall, FinalizerError, invoke_finalizer_async};
68pub use host::{
69 INTERNAL_MODULE_NAME, NUMBER_MODULE_NAME, host_package_declarations,
70 install_async as install_runtime_async, install_async_for as install_runtime_async_for,
71 install_host_functions as install_runtime_host_functions,
72 install_host_functions_for as install_runtime_host_functions_for,
73 install_store_bound as install_runtime_store_bound, internal_host_package_declarations,
74 stdlib_package_declarations,
75};
76pub use json::JSON_MODULE_NAME;
77pub use limits::{
78 DEFAULT_MAX_STORE_BYTES, MemoryCapExceeded, MemoryExhausted, TenantLimits,
79 install_tenant_limits, is_memory_exhausted,
80};
81pub use llm::{
82 DEFAULT_MAX_ALL_EXECUTIONS_TOKENS, DEFAULT_MAX_EXECUTION_TOKENS, ExecutionTokenBudget,
83 FailureReason, LLM_MODULE_NAME, LlmCallError, LlmFailure, LlmLimitKind, LlmLimits, LlmModel,
84 LlmOutcome, LlmProvider, PromptBoundKind, SharedTokenBudget,
85};
86pub use mcp::{
87 MCP_MODULE_NAME, McpCallError, McpOutcome, McpResponse, McpTransport, install_mcp_async,
88 mcp_call_package_declaration,
89};
90pub use metrics::{HttpMetric, MetricsSink, NoopMetricsSink};
91pub use model_calls::{AttributedUsage, ModelCallUsage};
92pub use prelude::bigint::ops::BIGINT_MODULE_NAME;
93pub use prelude::temporal::shared::TEMPORAL_MODULE_NAME;
94pub use secrets::{EncryptedSecret, NoopSecretProvider, SecretKeyId, SecretProvider};
95pub use security::{AllowAllCheck, AuditDecision, CheckOutcome, SecurityCheck, denial_reason};
96pub use session_kv::{
97 CURSOR_KEY_ID_LEN, CursorKey, CursorKeys, InMemorySessionKv, SessionKvEntry, SessionKvError,
98 SessionKvFuture, SessionKvLimitKind, SessionKvLimits, SessionKvPage, SessionKvStore,
99 SessionKvUsage, entry_bytes, render_key, value_bytes,
100};
101pub use skills::{SKILLS_MODULE_NAME, Skill, SkillError, SkillInfo, SkillProvider};
102pub use tools::{
103 TOOLS_CAPABILITY, TOOLS_MODULE_NAME, TOOLS_PACKAGE, ToolCallError, ToolProvider,
104 install_tools_async, tools_call_package_declaration,
105};
106pub use vfs::{
107 Access, CopyDirError, MAX_MEASURED_DEPTH, MountError, MountSpec, Vfs, VfsMode, copy_host_dir,
108 copy_host_subdir, measure_dir, measure_host_dir, measure_host_dir_skipping_vanished,
109 measure_host_subdir_skipping_vanished, open_host_subdir, regular_files,
110};
111pub use watchdog::Watchdog;
112
113pub use crate::stdlib::http::{
114 AuthProxy, AuthProxyError, HttpClient, HttpError, HttpRequest, HttpResponse, NetworkPolicy,
115 NoopAuthProxy, ReqwestHttpClient,
116};
117
118use std::cell::RefCell;
119use std::io::Write;
120use std::sync::{Arc, Mutex};
121use std::time::Duration;
122
123use wasmtime::{
124 ArrayRef, AsContextMut, Config, Engine, Instance, Linker, Module, OptLevel, Rooted, Store,
125 WasmBacktraceDetails,
126};
127
128use crate::{PackageDeclaration, TypeInfoTable};
129
130#[derive(Debug, Clone)]
134pub struct VfsInfo {
135 pub mode: VfsMode,
136 pub size_limit: Option<u64>,
137}
138
139pub(crate) type HostViews = Vec<(wasmtime::StructType, crate::stdlib::abi::HostProperties)>;
141
142pub struct StoreData {
143 #[cfg(test)]
144 pub(crate) array_growth: array_storage::GrowthStats,
145 pub blocking_work: blocking::BlockingWork,
148 pub git: Option<crate::stdlib::git::GitConfig>,
149 pub(crate) git_history: Option<Arc<crate::stdlib::git::log_cache::Cache>>,
150 pub console: Box<dyn Write + Send>,
151 pub vfs: Vfs,
152 pub filesystem: Arc<dyn filesystem::Filesystem>,
153 pub effects: Option<Arc<dyn effects::EffectHistory>>,
155 pub vfs_info: VfsInfo,
156 pub security_check: Arc<dyn SecurityCheck>,
157 pub fs_max_read_size: u64,
158 pub http_client: Arc<dyn HttpClient>,
159 pub http_max_response_size: u64,
160 pub http_max_download_timeout_ms: u64,
162 pub auth_proxy: Arc<dyn AuthProxy>,
163 pub variables: Arc<std::collections::BTreeMap<String, String>>,
165 pub secret_provider: Arc<dyn SecretProvider>,
166 pub mcp_transport: Option<Arc<dyn McpTransport>>,
170 pub tool_provider: Option<Arc<dyn ToolProvider>>,
174 pub approval_provider: Option<Arc<dyn ApprovalProvider>>,
178 pub clock_paused: std::time::Duration,
183 pub llm_provider: Option<Arc<dyn LlmProvider>>,
189 pub llm_budget: Option<Arc<ExecutionTokenBudget>>,
196 pub embedding_provider: Option<Arc<dyn EmbeddingProvider>>,
200 pub embedding_budget: Option<Arc<EmbeddingTokenBudget>>,
203 model_calls: Vec<ModelCallUsage>,
206 pub session_kv: Option<Arc<dyn SessionKvStore>>,
211 pub session_finalizers: Option<Arc<dyn session_finalizers::SessionFinalizers>>,
213 pub finalizing: bool,
214 pub cursor_keys: Option<Arc<CursorKeys>>,
217 pub agent_provider: Option<Arc<dyn AgentProvider>>,
221 pub skill_provider: Option<Arc<dyn SkillProvider>>,
225 pub metrics: Arc<dyn metrics::MetricsSink>,
228 pub tenant_limits: TenantLimits,
229 pub host_fuel: u64,
232 pub(crate) next_identity_hash: u64,
233 #[cfg(test)]
234 pub(crate) collection_index_reads: u64,
235 #[cfg(test)]
236 pub(crate) object_index_probes: u64,
237 pub host_fuel_pending: u64,
240 pub host_fuel_applied_at: Option<u64>,
243 pub test_labels: RefCell<Vec<String>>,
248 pub host_abi: Option<crate::runtime::host::HostAbi>,
253 pub(crate) intrinsic_types: Option<Arc<crate::runtime::intrinsic_types::IntrinsicTypes>>,
256 pub(crate) host_views: Option<HostViews>,
259 pub(crate) host_vtable_bodies: Vec<prelude::vtable::HostVtableBodies>,
261 pub(crate) undefined_value: Option<wasmtime::Global>,
263 pub(crate) closure_receiver_type: Option<wasmtime::StructType>,
266 pub(crate) call_metadata_type: Option<wasmtime::StructType>,
269 pub(crate) iterator_functions: [Option<wasmtime::Func>; 14],
270 pub(crate) iterator_constants: [Option<wasmtime::Global>; 6],
271 pub(crate) static_values: std::collections::BTreeMap<Vec<u16>, wasmtime::Global>,
274 pub(crate) parameter_cache: prelude::arguments::ParameterCache,
275 pub(crate) regex_input: Option<prelude::regex::input::InputCache>,
276 pub type_info: std::collections::BTreeMap<String, TypeInfoTable>,
278 pub vtable_walk_depth: u32,
281 pub(crate) vtable_walk_nodes: u32,
284 pub(crate) settling_host_result: bool,
286 pub(crate) thrown_denials: host::ThrownDenials,
288}
289
290pub(crate) const MAX_VTABLE_WALK_DEPTH: u32 = 128;
299
300pub(crate) const MAX_STRUCTURAL_WALK_NODES: u32 = 100_000;
302
303pub const DEFAULT_FS_MAX_READ_SIZE: u64 = 50 * 1024 * 1024;
304
305pub const DEFAULT_HTTP_MAX_RESPONSE_SIZE: u64 = 50 * 1024 * 1024;
306
307#[derive(Clone, Copy)]
308pub struct LinkedPackageModule<'a> {
309 pub module: &'a Module,
310 pub declaration: &'a PackageDeclaration,
311 pub type_info: &'a TypeInfoTable,
312}
313
314impl StoreData {
315 pub fn with_vfs(run_id: RunId, vfs: Vfs) -> Self {
316 Self::with_vfs_and_cap(run_id, vfs, DEFAULT_MAX_STORE_BYTES)
317 }
318
319 pub fn with_vfs_and_cap(run_id: RunId, vfs: Vfs, max_store_bytes: u64) -> Self {
320 let vfs_info = VfsInfo {
321 mode: vfs.mode(),
322 size_limit: None,
323 };
324 Self {
325 #[cfg(test)]
326 array_growth: array_storage::GrowthStats::default(),
327 console: Box::new(std::io::stderr()),
328 filesystem: Arc::new(default_filesystem::DefaultFilesystem::new(
329 vfs.clone(),
330 run_id,
331 )),
332 effects: None,
333 vfs,
334 vfs_info,
335 security_check: security::default_check(),
336 git: None,
337 git_history: None,
338 blocking_work: blocking::BlockingWork::default(),
339 fs_max_read_size: DEFAULT_FS_MAX_READ_SIZE,
340 http_client: crate::stdlib::http::default_http_client(),
341 http_max_response_size: DEFAULT_HTTP_MAX_RESPONSE_SIZE,
342 http_max_download_timeout_ms: 60_000,
343 auth_proxy: crate::stdlib::http::default_auth_proxy(),
344 variables: Arc::default(),
345 secret_provider: Arc::new(secrets::NoopSecretProvider),
346 mcp_transport: None,
347 tool_provider: None,
348 approval_provider: None,
349 clock_paused: std::time::Duration::ZERO,
350 llm_provider: None,
351 llm_budget: None,
352 embedding_provider: None,
353 embedding_budget: None,
354 model_calls: Vec::new(),
355 session_kv: None,
356 session_finalizers: None,
357 finalizing: false,
358 cursor_keys: None,
359 agent_provider: None,
360 skill_provider: None,
361 metrics: Arc::new(metrics::NoopMetricsSink),
362 tenant_limits: TenantLimits::new(max_store_bytes),
363 host_fuel: 0,
364 #[cfg(test)]
365 collection_index_reads: 0,
366 #[cfg(test)]
367 object_index_probes: 0,
368 next_identity_hash: 0,
369 host_fuel_pending: 0,
370 host_fuel_applied_at: None,
371 test_labels: RefCell::new(Vec::new()),
372 host_abi: None,
373 intrinsic_types: None,
374 host_views: None,
375 host_vtable_bodies: Vec::new(),
376 undefined_value: None,
377 closure_receiver_type: None,
378 call_metadata_type: None,
379 iterator_functions: [None; 14],
380 iterator_constants: [None; 6],
381 static_values: std::collections::BTreeMap::new(),
382 parameter_cache: Default::default(),
383 regex_input: None,
384 type_info: std::collections::BTreeMap::new(),
385 vtable_walk_depth: 0,
386 vtable_walk_nodes: 0,
387 settling_host_result: false,
388 thrown_denials: host::ThrownDenials::default(),
389 }
390 }
391
392 pub fn install_type_info(&mut self, table: TypeInfoTable) {
393 self.type_info.insert(table.package_name.clone(), table);
394 }
395
396 pub fn model_calls(&self) -> &[ModelCallUsage] {
402 &self.model_calls
403 }
404
405 pub(crate) fn note_model_call(&mut self, call: ModelCallUsage) {
410 self.model_calls.push(call);
411 }
412
413 pub fn with_tempdir() -> std::io::Result<Self> {
414 Ok(Self::with_vfs(RunId::generate()?, Vfs::tempdir()?))
415 }
416}
417
418pub async fn install_package_modules_async(
423 linker: &mut Linker<StoreData>,
424 store: &mut Store<StoreData>,
425 packages: &[LinkedPackageModule<'_>],
426) -> wasmtime::Result<()> {
427 for package in packages {
428 let name = package.declaration.package_name.as_str();
429 if store.data().git.is_none()
430 && package
431 .module
432 .imports()
433 .any(|import| import.module() == "submilli:git")
434 {
435 wasmtime::bail!(
436 "package `{name}` imports submilli:git; configure the blueprint git block"
437 );
438 }
439 match package.module.name() {
446 Some(declared) if declared == name => {}
447 Some(declared) => wasmtime::bail!(
448 "package `{name}`: its module declares the name `{declared}`, which would give \
449 it that principal's permissions. Rebuild the package."
450 ),
451 None => wasmtime::bail!(
452 "package `{name}`: its module declares no name, so its gated calls cannot be \
453 attributed. Rebuild the package."
454 ),
455 }
456 store
457 .data_mut()
458 .install_type_info(package.type_info.clone());
459 let instance = {
463 let outcome = linker.instantiate_async(&mut *store, package.module).await;
464 name_the_failing_initializer(&mut *store, name, outcome)?
465 };
466 linker.instance(&mut *store, name, instance)?;
467 }
468 Ok(())
469}
470
471fn name_the_failing_initializer(
480 store: &mut Store<StoreData>,
481 package: &str,
482 outcome: wasmtime::Result<Instance>,
483) -> wasmtime::Result<Instance> {
484 let err = match outcome {
485 Ok(instance) => return Ok(instance),
486 Err(err) => err,
487 };
488 let recovered = exec::uncaught_error(store, err);
489 let Some(thrown) = recovered.downcast_ref::<crate::backtrace::ThrownError>() else {
490 return Err(recovered);
491 };
492 Err(wasmtime::Error::new(crate::backtrace::ThrownError {
493 message: format!(
494 "package `{package}` failed to initialize: {}",
495 thrown.message
496 ),
497 backtrace: thrown.backtrace.clone(),
498 denial: thrown.denial.clone(),
499 }))
500}
501
502const NATIVE_STACK_PER_WASM_BYTE: usize = 32;
505const MIN_NATIVE_STACK: usize = 8 * 1024 * 1024;
506
507#[derive(Clone, Debug)]
508pub struct RuntimeConfig {
509 pub fuel: u64,
510 pub max_wasm_stack: usize,
511 pub memory_reservation: u64,
512 pub memory_guard_size: u64,
513 pub memory_reservation_for_growth: u64,
514 pub max_store_bytes: u64,
515 pub timeout: Option<Duration>,
516 pub async_yield_fuel: Option<u64>,
517}
518
519impl Default for RuntimeConfig {
520 fn default() -> Self {
521 Self {
522 fuel: 1_000_000_000_000,
528 max_wasm_stack: 512 * 1024,
529 memory_reservation: 3 * 1024 * 1024,
530 memory_guard_size: 64 * 1024,
531 memory_reservation_for_growth: 16 * 1024 * 1024,
532 max_store_bytes: DEFAULT_MAX_STORE_BYTES,
533 timeout: None,
534 async_yield_fuel: Some(10_000),
539 }
540 }
541}
542
543impl RuntimeConfig {
544 pub fn native_stack_size(&self) -> usize {
565 self.max_wasm_stack
566 .saturating_mul(NATIVE_STACK_PER_WASM_BYTE)
567 .max(MIN_NATIVE_STACK)
568 }
569
570 pub fn engine(&self) -> wasmtime::Result<Engine> {
571 Engine::new(&self.wasmtime_config())
572 }
573
574 pub fn engine_async(&self) -> wasmtime::Result<Engine> {
578 Engine::new(&self.wasmtime_config())
579 }
580
581 pub fn wasmtime_config(&self) -> Config {
582 let mut config = Config::new();
583 config.cranelift_opt_level(OptLevel::None);
594 config.consume_fuel(true);
595 config.max_wasm_stack(self.max_wasm_stack);
596
597 config.epoch_interruption(true);
598
599 config.memory_reservation(self.memory_reservation);
600 config.memory_guard_size(self.memory_guard_size);
601 config.memory_reservation_for_growth(self.memory_reservation_for_growth);
602 config.memory_may_move(true);
603 config.memory_init_cow(true);
604
605 config.gc_heap_reservation(0);
609 config.gc_heap_guard_size(self.memory_guard_size);
610 config.gc_heap_reservation_for_growth(self.memory_reservation_for_growth);
611 config.gc_heap_may_move(true);
612
613 config.wasm_gc(true);
614 config.wasm_function_references(true);
615 config.wasm_exceptions(true);
616
617 config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
618
619 config
620 }
621
622 pub fn store<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
623 let mut store = Store::new(engine, data);
624 store.set_fuel(self.fuel)?;
625 let delta = if self.timeout.is_some() { 1 } else { u64::MAX };
628 store.set_epoch_deadline(delta);
629 store.epoch_deadline_trap();
630 Ok(store)
631 }
632
633 pub fn store_async<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
634 let mut store = self.store(engine, data)?;
635 store.fuel_async_yield_interval(self.async_yield_fuel)?;
636 Ok(store)
637 }
638
639 pub fn arm_timeout(&self, engine: &Engine) -> wasmtime::Result<Option<Watchdog>> {
642 self.timeout
643 .map(|duration| watchdog::arm(engine, duration))
644 .transpose()
645 .map_err(|error| {
646 wasmtime::Error::from(error).context("starting execution timeout watchdog")
647 })
648 }
649
650 pub async fn run(&self, wasm_bytes: &[u8]) -> wasmtime::Result<RunResult> {
663 self.run_with_type_info(wasm_bytes, None).await
664 }
665
666 pub async fn run_compiled(
674 &self,
675 compiled: &crate::compile::CompiledScript,
676 ) -> wasmtime::Result<RunResult> {
677 self.run_with_type_info(&compiled.wasm, Some(compiled.type_info.clone()))
678 .await
679 }
680
681 async fn run_with_type_info(
682 &self,
683 wasm_bytes: &[u8],
684 type_info: Option<TypeInfoTable>,
685 ) -> wasmtime::Result<RunResult> {
686 let buf = Arc::new(Mutex::new(Vec::new()));
687 let mut data =
688 StoreData::with_vfs_and_cap(RunId::generate()?, Vfs::tempdir()?, self.max_store_bytes);
689 data.console = Box::new(ConsoleSink(Arc::clone(&buf)));
690 if let Some(type_info) = type_info {
691 data.install_type_info(type_info);
692 }
693 let engine = self.engine()?;
694 let mut store = self.store_async(&engine, data)?;
695 install_tenant_limits(&mut store);
696 let module = Module::new(&engine, wasm_bytes)?;
697 let mut linker = Linker::<StoreData>::new(&engine);
698 install_runtime_async(&mut linker, &mut store).await?;
699 let _watchdog = self.arm_timeout(&engine)?;
700 let inst = instantiate_program_async(&linker, &mut store, &module).await?;
701 let value = dispatch_main_async(&mut store, &inst).await?;
702 let captured = buf.lock().expect("console buffer lock poisoned").clone();
706 let console = String::from_utf8(captured)
707 .map_err(|e| wasmtime::Error::msg(format!("console output not utf-8: {e}")))?;
708 Ok(RunResult { value, console })
709 }
710}
711
712struct ConsoleSink(Arc<Mutex<Vec<u8>>>);
716
717impl Write for ConsoleSink {
718 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
719 self.0
720 .lock()
721 .expect("console buffer lock poisoned")
722 .write(buf)
723 }
724
725 fn flush(&mut self) -> std::io::Result<()> {
726 Ok(())
727 }
728}
729
730pub(crate) fn read_submilli_string(
732 mut ctx: impl AsContextMut<Data = StoreData>,
733 msg: Rooted<ArrayRef>,
734) -> wasmtime::Result<String> {
735 let units = host::read_code_units(&mut ctx, msg, "string")?;
736 fuel::charge(ctx, fuel::SCAN, units.len() as u64)?;
737 Ok(String::from_utf16_lossy(&units))
738}