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) undefined_value: Option<wasmtime::Global>,
261 pub(crate) closure_receiver_type: Option<wasmtime::StructType>,
264 pub(crate) call_metadata_type: Option<wasmtime::StructType>,
267 pub(crate) iterator_functions: [Option<wasmtime::Func>; 14],
268 pub(crate) iterator_constants: [Option<wasmtime::Global>; 6],
269 pub(crate) static_values: std::collections::BTreeMap<Vec<u16>, wasmtime::Global>,
272 pub(crate) parameter_cache: prelude::arguments::ParameterCache,
273 pub(crate) regex_input: Option<prelude::regex::input::InputCache>,
274 pub type_info: std::collections::BTreeMap<String, TypeInfoTable>,
276 pub vtable_walk_depth: u32,
279 pub(crate) vtable_walk_nodes: u32,
282 pub(crate) settling_host_result: bool,
284 pub(crate) thrown_denials: host::ThrownDenials,
286}
287
288pub(crate) const MAX_VTABLE_WALK_DEPTH: u32 = 128;
297
298pub(crate) const MAX_STRUCTURAL_WALK_NODES: u32 = 100_000;
300
301pub const DEFAULT_FS_MAX_READ_SIZE: u64 = 50 * 1024 * 1024;
302
303pub const DEFAULT_HTTP_MAX_RESPONSE_SIZE: u64 = 50 * 1024 * 1024;
304
305#[derive(Clone, Copy)]
306pub struct LinkedPackageModule<'a> {
307 pub module: &'a Module,
308 pub declaration: &'a PackageDeclaration,
309 pub type_info: &'a TypeInfoTable,
310}
311
312impl StoreData {
313 pub fn with_vfs(run_id: RunId, vfs: Vfs) -> Self {
314 Self::with_vfs_and_cap(run_id, vfs, DEFAULT_MAX_STORE_BYTES)
315 }
316
317 pub fn with_vfs_and_cap(run_id: RunId, vfs: Vfs, max_store_bytes: u64) -> Self {
318 let vfs_info = VfsInfo {
319 mode: vfs.mode(),
320 size_limit: None,
321 };
322 Self {
323 #[cfg(test)]
324 array_growth: array_storage::GrowthStats::default(),
325 console: Box::new(std::io::stderr()),
326 filesystem: Arc::new(default_filesystem::DefaultFilesystem::new(
327 vfs.clone(),
328 run_id,
329 )),
330 effects: None,
331 vfs,
332 vfs_info,
333 security_check: security::default_check(),
334 git: None,
335 git_history: None,
336 blocking_work: blocking::BlockingWork::default(),
337 fs_max_read_size: DEFAULT_FS_MAX_READ_SIZE,
338 http_client: crate::stdlib::http::default_http_client(),
339 http_max_response_size: DEFAULT_HTTP_MAX_RESPONSE_SIZE,
340 http_max_download_timeout_ms: 60_000,
341 auth_proxy: crate::stdlib::http::default_auth_proxy(),
342 variables: Arc::default(),
343 secret_provider: Arc::new(secrets::NoopSecretProvider),
344 mcp_transport: None,
345 tool_provider: None,
346 approval_provider: None,
347 clock_paused: std::time::Duration::ZERO,
348 llm_provider: None,
349 llm_budget: None,
350 embedding_provider: None,
351 embedding_budget: None,
352 model_calls: Vec::new(),
353 session_kv: None,
354 session_finalizers: None,
355 finalizing: false,
356 cursor_keys: None,
357 agent_provider: None,
358 skill_provider: None,
359 metrics: Arc::new(metrics::NoopMetricsSink),
360 tenant_limits: TenantLimits::new(max_store_bytes),
361 host_fuel: 0,
362 #[cfg(test)]
363 collection_index_reads: 0,
364 #[cfg(test)]
365 object_index_probes: 0,
366 next_identity_hash: 0,
367 host_fuel_pending: 0,
368 host_fuel_applied_at: None,
369 test_labels: RefCell::new(Vec::new()),
370 host_abi: None,
371 intrinsic_types: None,
372 host_views: None,
373 undefined_value: None,
374 closure_receiver_type: None,
375 call_metadata_type: None,
376 iterator_functions: [None; 14],
377 iterator_constants: [None; 6],
378 static_values: std::collections::BTreeMap::new(),
379 parameter_cache: Default::default(),
380 regex_input: None,
381 type_info: std::collections::BTreeMap::new(),
382 vtable_walk_depth: 0,
383 vtable_walk_nodes: 0,
384 settling_host_result: false,
385 thrown_denials: host::ThrownDenials::default(),
386 }
387 }
388
389 pub fn install_type_info(&mut self, table: TypeInfoTable) {
390 self.type_info.insert(table.package_name.clone(), table);
391 }
392
393 pub fn model_calls(&self) -> &[ModelCallUsage] {
399 &self.model_calls
400 }
401
402 pub(crate) fn note_model_call(&mut self, call: ModelCallUsage) {
407 self.model_calls.push(call);
408 }
409
410 pub fn with_tempdir() -> std::io::Result<Self> {
411 Ok(Self::with_vfs(RunId::generate()?, Vfs::tempdir()?))
412 }
413}
414
415pub async fn install_package_modules_async(
420 linker: &mut Linker<StoreData>,
421 store: &mut Store<StoreData>,
422 packages: &[LinkedPackageModule<'_>],
423) -> wasmtime::Result<()> {
424 for package in packages {
425 let name = package.declaration.package_name.as_str();
426 if store.data().git.is_none()
427 && package
428 .module
429 .imports()
430 .any(|import| import.module() == "submilli:git")
431 {
432 wasmtime::bail!(
433 "package `{name}` imports submilli:git; configure the blueprint git block"
434 );
435 }
436 match package.module.name() {
443 Some(declared) if declared == name => {}
444 Some(declared) => wasmtime::bail!(
445 "package `{name}`: its module declares the name `{declared}`, which would give \
446 it that principal's permissions. Rebuild the package."
447 ),
448 None => wasmtime::bail!(
449 "package `{name}`: its module declares no name, so its gated calls cannot be \
450 attributed. Rebuild the package."
451 ),
452 }
453 store
454 .data_mut()
455 .install_type_info(package.type_info.clone());
456 let instance = {
460 let outcome = linker.instantiate_async(&mut *store, package.module).await;
461 name_the_failing_initializer(&mut *store, name, outcome)?
462 };
463 linker.instance(&mut *store, name, instance)?;
464 }
465 Ok(())
466}
467
468fn name_the_failing_initializer(
477 store: &mut Store<StoreData>,
478 package: &str,
479 outcome: wasmtime::Result<Instance>,
480) -> wasmtime::Result<Instance> {
481 let err = match outcome {
482 Ok(instance) => return Ok(instance),
483 Err(err) => err,
484 };
485 let recovered = exec::uncaught_error(store, err);
486 let Some(thrown) = recovered.downcast_ref::<crate::backtrace::ThrownError>() else {
487 return Err(recovered);
488 };
489 Err(wasmtime::Error::new(crate::backtrace::ThrownError {
490 message: format!(
491 "package `{package}` failed to initialize: {}",
492 thrown.message
493 ),
494 backtrace: thrown.backtrace.clone(),
495 denial: thrown.denial.clone(),
496 }))
497}
498
499const NATIVE_STACK_PER_WASM_BYTE: usize = 32;
502const MIN_NATIVE_STACK: usize = 8 * 1024 * 1024;
503
504#[derive(Clone, Debug)]
505pub struct RuntimeConfig {
506 pub fuel: u64,
507 pub max_wasm_stack: usize,
508 pub memory_reservation: u64,
509 pub memory_guard_size: u64,
510 pub memory_reservation_for_growth: u64,
511 pub max_store_bytes: u64,
512 pub timeout: Option<Duration>,
513 pub async_yield_fuel: Option<u64>,
514}
515
516impl Default for RuntimeConfig {
517 fn default() -> Self {
518 Self {
519 fuel: 1_000_000_000_000,
525 max_wasm_stack: 512 * 1024,
526 memory_reservation: 3 * 1024 * 1024,
527 memory_guard_size: 64 * 1024,
528 memory_reservation_for_growth: 16 * 1024 * 1024,
529 max_store_bytes: DEFAULT_MAX_STORE_BYTES,
530 timeout: None,
531 async_yield_fuel: Some(10_000),
536 }
537 }
538}
539
540impl RuntimeConfig {
541 pub fn native_stack_size(&self) -> usize {
563 self.max_wasm_stack
564 .saturating_mul(NATIVE_STACK_PER_WASM_BYTE)
565 .max(MIN_NATIVE_STACK)
566 }
567
568 pub fn engine(&self) -> wasmtime::Result<Engine> {
569 Engine::new(&self.wasmtime_config())
570 }
571
572 pub fn engine_async(&self) -> wasmtime::Result<Engine> {
576 Engine::new(&self.wasmtime_config())
577 }
578
579 pub fn wasmtime_config(&self) -> Config {
580 let mut config = Config::new();
581 config.cranelift_opt_level(OptLevel::None);
592 config.consume_fuel(true);
593 config.max_wasm_stack(self.max_wasm_stack);
594
595 config.epoch_interruption(true);
596
597 config.memory_reservation(self.memory_reservation);
598 config.memory_guard_size(self.memory_guard_size);
599 config.memory_reservation_for_growth(self.memory_reservation_for_growth);
600 config.memory_may_move(true);
601 config.memory_init_cow(true);
602
603 config.gc_heap_reservation(0);
607 config.gc_heap_guard_size(self.memory_guard_size);
608 config.gc_heap_reservation_for_growth(self.memory_reservation_for_growth);
609 config.gc_heap_may_move(true);
610
611 config.wasm_gc(true);
612 config.wasm_function_references(true);
613 config.wasm_exceptions(true);
614
615 config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
616
617 config
618 }
619
620 pub fn store<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
621 let mut store = Store::new(engine, data);
622 store.set_fuel(self.fuel)?;
623 let delta = if self.timeout.is_some() { 1 } else { u64::MAX };
626 store.set_epoch_deadline(delta);
627 store.epoch_deadline_trap();
628 Ok(store)
629 }
630
631 pub fn store_async<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
632 let mut store = self.store(engine, data)?;
633 store.fuel_async_yield_interval(self.async_yield_fuel)?;
634 Ok(store)
635 }
636
637 pub fn arm_timeout(&self, engine: &Engine) -> wasmtime::Result<Option<Watchdog>> {
640 self.timeout
641 .map(|duration| watchdog::arm(engine, duration))
642 .transpose()
643 .map_err(|error| {
644 wasmtime::Error::from(error).context("starting execution timeout watchdog")
645 })
646 }
647
648 pub async fn run(&self, wasm_bytes: &[u8]) -> wasmtime::Result<RunResult> {
661 self.run_with_type_info(wasm_bytes, None).await
662 }
663
664 pub async fn run_compiled(
672 &self,
673 compiled: &crate::compile::CompiledScript,
674 ) -> wasmtime::Result<RunResult> {
675 self.run_with_type_info(&compiled.wasm, Some(compiled.type_info.clone()))
676 .await
677 }
678
679 async fn run_with_type_info(
680 &self,
681 wasm_bytes: &[u8],
682 type_info: Option<TypeInfoTable>,
683 ) -> wasmtime::Result<RunResult> {
684 let buf = Arc::new(Mutex::new(Vec::new()));
685 let mut data =
686 StoreData::with_vfs_and_cap(RunId::generate()?, Vfs::tempdir()?, self.max_store_bytes);
687 data.console = Box::new(ConsoleSink(Arc::clone(&buf)));
688 if let Some(type_info) = type_info {
689 data.install_type_info(type_info);
690 }
691 let engine = self.engine()?;
692 let mut store = self.store_async(&engine, data)?;
693 install_tenant_limits(&mut store);
694 let module = Module::new(&engine, wasm_bytes)?;
695 let mut linker = Linker::<StoreData>::new(&engine);
696 install_runtime_async(&mut linker, &mut store).await?;
697 let _watchdog = self.arm_timeout(&engine)?;
698 let inst = instantiate_program_async(&linker, &mut store, &module).await?;
699 let value = dispatch_main_async(&mut store, &inst).await?;
700 let captured = buf.lock().expect("console buffer lock poisoned").clone();
704 let console = String::from_utf8(captured)
705 .map_err(|e| wasmtime::Error::msg(format!("console output not utf-8: {e}")))?;
706 Ok(RunResult { value, console })
707 }
708}
709
710struct ConsoleSink(Arc<Mutex<Vec<u8>>>);
714
715impl Write for ConsoleSink {
716 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
717 self.0
718 .lock()
719 .expect("console buffer lock poisoned")
720 .write(buf)
721 }
722
723 fn flush(&mut self) -> std::io::Result<()> {
724 Ok(())
725 }
726}
727
728pub(crate) fn read_submilli_string(
730 mut ctx: impl AsContextMut<Data = StoreData>,
731 msg: Rooted<ArrayRef>,
732) -> wasmtime::Result<String> {
733 let units = host::read_code_units(&mut ctx, msg, "string")?;
734 fuel::charge(ctx, fuel::SCAN, units.len() as u64)?;
735 Ok(String::from_utf16_lossy(&units))
736}