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 finalizers;
18pub mod fs;
19pub mod fuel;
20pub mod gc_singleton;
21pub mod host;
22pub mod intrinsic_types;
23pub mod json;
24pub(crate) mod json_text;
25pub mod limits;
26pub mod llm;
27pub mod mcp;
28pub mod metrics;
29pub mod model_calls;
30pub mod number;
31pub mod prelude;
32pub mod secrets;
33pub mod security;
34pub mod session_finalizers;
35pub mod session_kv;
36pub mod skills;
37pub mod token_ledger;
38pub mod tools;
39pub mod vfs;
40pub mod watchdog;
41
42pub use agents::{
43 AGENTS_MODULE_NAME, AgentCallError, AgentInfo, AgentOutcome, AgentProvider, AgentRequest,
44 AgentUsage,
45};
46pub use call_log::{BodyCopy, CallOutcome, CallRecord, ModelUsage, PayloadRecord};
47pub use decision::{
48 CallOrigin, CallSite, CallTicket, ConsoleLine, DecisionAction, DecisionCause,
49 DecisionExplanation, DecisionLog, DecisionLogConfig, DecisionLogOutput, DecisionRecord,
50 DecisionRecorder, EntryPath, FailureReasonRecord, FailureRecord, NearMissRecord, PackageLine,
51 RecordObserver, RuleCitation, SourceLine,
52};
53pub use disk_quota::{DiskQuota, Holder, OpenFileGuard, QuotaCharge, QuotaExceeded};
54pub use embedding::{
55 DEFAULT_MAX_EMBEDDING_REQUESTS, DEFAULT_MAX_EXECUTION_EMBEDDING_TOKENS, EMBEDDING_MODULE_NAME,
56 EmbeddingBatch, EmbeddingBoundKind, EmbeddingError, EmbeddingFailureReason, EmbeddingLimitKind,
57 EmbeddingLimits, EmbeddingMalformedReason, EmbeddingModel, EmbeddingProvider,
58 EmbeddingShapeError, EmbeddingTokenBudget, Purpose, SubBatchSettlement,
59 estimate_embedding_tokens,
60};
61pub use exec::{RunResult, dispatch_main_async, instantiate_program_async};
62pub use finalizers::{FinalizerCall, FinalizerError, invoke_finalizer_async};
63pub use host::{
64 INTERNAL_MODULE_NAME, NUMBER_MODULE_NAME, host_package_declarations,
65 install_async as install_runtime_async, install_async_for as install_runtime_async_for,
66 install_host_functions as install_runtime_host_functions,
67 install_host_functions_for as install_runtime_host_functions_for,
68 install_store_bound as install_runtime_store_bound, internal_host_package_declarations,
69 stdlib_package_declarations,
70};
71pub use json::JSON_MODULE_NAME;
72pub use limits::{
73 DEFAULT_MAX_STORE_BYTES, MemoryCapExceeded, MemoryExhausted, TenantLimits,
74 install_tenant_limits, is_memory_exhausted,
75};
76pub use llm::{
77 DEFAULT_MAX_ALL_EXECUTIONS_TOKENS, DEFAULT_MAX_EXECUTION_TOKENS, ExecutionTokenBudget,
78 FailureReason, LLM_MODULE_NAME, LlmCallError, LlmFailure, LlmLimitKind, LlmLimits, LlmModel,
79 LlmOutcome, LlmProvider, PromptBoundKind, SharedTokenBudget,
80};
81pub use mcp::{
82 MCP_MODULE_NAME, McpCallError, McpOutcome, McpResponse, McpTransport, install_mcp_async,
83 mcp_call_package_declaration,
84};
85pub use metrics::{HttpMetric, MetricsSink, NoopMetricsSink};
86pub use model_calls::{AttributedUsage, ModelCallUsage};
87pub use prelude::bigint::ops::BIGINT_MODULE_NAME;
88pub use prelude::temporal::shared::TEMPORAL_MODULE_NAME;
89pub use secrets::{EncryptedSecret, NoopSecretProvider, SecretKeyId, SecretProvider};
90pub use security::{AllowAllCheck, AuditDecision, CheckOutcome, SecurityCheck};
91pub use session_kv::{
92 CURSOR_KEY_ID_LEN, CursorKey, CursorKeys, InMemorySessionKv, SessionKvEntry, SessionKvError,
93 SessionKvFuture, SessionKvLimitKind, SessionKvLimits, SessionKvPage, SessionKvStore,
94 SessionKvUsage, entry_bytes, render_key, value_bytes,
95};
96pub use skills::{SKILLS_MODULE_NAME, Skill, SkillError, SkillInfo, SkillProvider};
97pub use tools::{
98 TOOLS_CAPABILITY, TOOLS_MODULE_NAME, TOOLS_PACKAGE, ToolCallError, ToolProvider,
99 install_tools_async, tools_call_package_declaration,
100};
101pub use vfs::{
102 Access, CopyDirError, MAX_MEASURED_DEPTH, MountError, MountSpec, Vfs, VfsMode, copy_host_dir,
103 copy_host_subdir, measure_dir, measure_host_dir, measure_host_dir_skipping_vanished,
104 measure_host_subdir_skipping_vanished, open_host_subdir, regular_files,
105};
106pub use watchdog::Watchdog;
107
108pub use crate::stdlib::http::{
109 AuthProxy, AuthProxyError, HttpClient, HttpError, HttpRequest, HttpResponse, NetworkPolicy,
110 NoopAuthProxy, ReqwestHttpClient,
111};
112
113use std::cell::RefCell;
114use std::io::Write;
115use std::sync::{Arc, Mutex};
116use std::time::Duration;
117
118use wasmtime::{
119 ArrayRef, AsContextMut, Config, Engine, Instance, Linker, Module, OptLevel, Rooted, Store,
120 WasmBacktraceDetails,
121};
122
123use crate::{PackageDeclaration, TypeInfoTable};
124
125#[derive(Debug, Clone)]
129pub struct VfsInfo {
130 pub mode: VfsMode,
131 pub size_limit: Option<u64>,
132}
133
134pub(crate) type HostViews = Vec<(wasmtime::StructType, crate::stdlib::abi::HostProperties)>;
136
137pub struct StoreData {
138 #[cfg(test)]
139 pub(crate) array_growth: array_storage::GrowthStats,
140 pub blocking_work: blocking::BlockingWork,
143 pub git: Option<crate::stdlib::git::GitConfig>,
144 pub(crate) git_history: Option<Arc<crate::stdlib::git::log_cache::Cache>>,
145 pub console: Box<dyn Write + Send>,
146 pub vfs: Vfs,
147 pub filesystem: Arc<dyn filesystem::Filesystem>,
148 pub effects: Option<Arc<dyn effects::EffectHistory>>,
150 pub vfs_info: VfsInfo,
151 pub security_check: Arc<dyn SecurityCheck>,
152 pub fs_max_read_size: u64,
153 pub http_client: Arc<dyn HttpClient>,
154 pub http_max_response_size: u64,
155 pub http_max_download_timeout_ms: u64,
157 pub auth_proxy: Arc<dyn AuthProxy>,
158 pub variables: Arc<std::collections::BTreeMap<String, String>>,
160 pub secret_provider: Arc<dyn SecretProvider>,
161 pub mcp_transport: Option<Arc<dyn McpTransport>>,
165 pub tool_provider: Option<Arc<dyn ToolProvider>>,
169 pub clock_paused: std::time::Duration,
173 pub llm_provider: Option<Arc<dyn LlmProvider>>,
179 pub llm_budget: Option<Arc<ExecutionTokenBudget>>,
186 pub embedding_provider: Option<Arc<dyn EmbeddingProvider>>,
190 pub embedding_budget: Option<Arc<EmbeddingTokenBudget>>,
193 model_calls: Vec<ModelCallUsage>,
196 pub session_kv: Option<Arc<dyn SessionKvStore>>,
201 pub session_finalizers: Option<Arc<dyn session_finalizers::SessionFinalizers>>,
203 pub finalizing: bool,
204 pub cursor_keys: Option<Arc<CursorKeys>>,
207 pub agent_provider: Option<Arc<dyn AgentProvider>>,
211 pub skill_provider: Option<Arc<dyn SkillProvider>>,
215 pub metrics: Arc<dyn metrics::MetricsSink>,
218 pub tenant_limits: TenantLimits,
219 pub host_fuel: u64,
222 pub(crate) next_identity_hash: u64,
223 #[cfg(test)]
224 pub(crate) collection_index_reads: u64,
225 #[cfg(test)]
226 pub(crate) object_index_probes: u64,
227 pub host_fuel_pending: u64,
230 pub host_fuel_applied_at: Option<u64>,
233 pub test_labels: RefCell<Vec<String>>,
238 pub host_abi: Option<crate::runtime::host::HostAbi>,
243 pub(crate) intrinsic_types: Option<Arc<crate::runtime::intrinsic_types::IntrinsicTypes>>,
246 pub(crate) host_views: Option<HostViews>,
249 pub(crate) undefined_value: Option<wasmtime::Global>,
251 pub(crate) closure_receiver_type: Option<wasmtime::StructType>,
254 pub(crate) call_metadata_type: Option<wasmtime::StructType>,
257 pub(crate) iterator_functions: [Option<wasmtime::Func>; 13],
258 pub(crate) iterator_constants: [Option<wasmtime::Global>; 6],
259 pub(crate) static_values: std::collections::BTreeMap<Vec<u16>, wasmtime::Global>,
262 pub(crate) parameter_cache: prelude::arguments::ParameterCache,
263 pub(crate) regex_input: Option<prelude::regex::input::InputCache>,
264 pub type_info: std::collections::BTreeMap<String, TypeInfoTable>,
266 pub vtable_walk_depth: u32,
269 pub(crate) vtable_walk_nodes: u32,
272 pub(crate) settling_host_result: bool,
274 pub(crate) thrown_denials: host::ThrownDenials,
276}
277
278pub(crate) const MAX_VTABLE_WALK_DEPTH: u32 = 128;
287
288pub(crate) const MAX_STRUCTURAL_WALK_NODES: u32 = 100_000;
290
291pub const DEFAULT_FS_MAX_READ_SIZE: u64 = 50 * 1024 * 1024;
292
293pub const DEFAULT_HTTP_MAX_RESPONSE_SIZE: u64 = 50 * 1024 * 1024;
294
295#[derive(Clone, Copy)]
296pub struct LinkedPackageModule<'a> {
297 pub module: &'a Module,
298 pub declaration: &'a PackageDeclaration,
299 pub type_info: &'a TypeInfoTable,
300}
301
302impl StoreData {
303 pub fn with_vfs(run_id: RunId, vfs: Vfs) -> Self {
304 Self::with_vfs_and_cap(run_id, vfs, DEFAULT_MAX_STORE_BYTES)
305 }
306
307 pub fn with_vfs_and_cap(run_id: RunId, vfs: Vfs, max_store_bytes: u64) -> Self {
308 let vfs_info = VfsInfo {
309 mode: vfs.mode(),
310 size_limit: None,
311 };
312 Self {
313 #[cfg(test)]
314 array_growth: array_storage::GrowthStats::default(),
315 console: Box::new(std::io::stderr()),
316 filesystem: Arc::new(default_filesystem::DefaultFilesystem::new(
317 vfs.clone(),
318 run_id,
319 )),
320 effects: None,
321 vfs,
322 vfs_info,
323 security_check: security::default_check(),
324 git: None,
325 git_history: None,
326 blocking_work: blocking::BlockingWork::default(),
327 fs_max_read_size: DEFAULT_FS_MAX_READ_SIZE,
328 http_client: crate::stdlib::http::default_http_client(),
329 http_max_response_size: DEFAULT_HTTP_MAX_RESPONSE_SIZE,
330 http_max_download_timeout_ms: 60_000,
331 auth_proxy: crate::stdlib::http::default_auth_proxy(),
332 variables: Arc::default(),
333 secret_provider: Arc::new(secrets::NoopSecretProvider),
334 mcp_transport: None,
335 tool_provider: None,
336 clock_paused: std::time::Duration::ZERO,
337 llm_provider: None,
338 llm_budget: None,
339 embedding_provider: None,
340 embedding_budget: None,
341 model_calls: Vec::new(),
342 session_kv: None,
343 session_finalizers: None,
344 finalizing: false,
345 cursor_keys: None,
346 agent_provider: None,
347 skill_provider: None,
348 metrics: Arc::new(metrics::NoopMetricsSink),
349 tenant_limits: TenantLimits::new(max_store_bytes),
350 host_fuel: 0,
351 #[cfg(test)]
352 collection_index_reads: 0,
353 #[cfg(test)]
354 object_index_probes: 0,
355 next_identity_hash: 0,
356 host_fuel_pending: 0,
357 host_fuel_applied_at: None,
358 test_labels: RefCell::new(Vec::new()),
359 host_abi: None,
360 intrinsic_types: None,
361 host_views: None,
362 undefined_value: None,
363 closure_receiver_type: None,
364 call_metadata_type: None,
365 iterator_functions: [None; 13],
366 iterator_constants: [None; 6],
367 static_values: std::collections::BTreeMap::new(),
368 parameter_cache: Default::default(),
369 regex_input: None,
370 type_info: std::collections::BTreeMap::new(),
371 vtable_walk_depth: 0,
372 vtable_walk_nodes: 0,
373 settling_host_result: false,
374 thrown_denials: host::ThrownDenials::default(),
375 }
376 }
377
378 pub fn install_type_info(&mut self, table: TypeInfoTable) {
379 self.type_info.insert(table.package_name.clone(), table);
380 }
381
382 pub fn model_calls(&self) -> &[ModelCallUsage] {
388 &self.model_calls
389 }
390
391 pub(crate) fn note_model_call(&mut self, call: ModelCallUsage) {
396 self.model_calls.push(call);
397 }
398
399 pub fn with_tempdir() -> std::io::Result<Self> {
400 Ok(Self::with_vfs(RunId::generate()?, Vfs::tempdir()?))
401 }
402}
403
404pub async fn install_package_modules_async(
409 linker: &mut Linker<StoreData>,
410 store: &mut Store<StoreData>,
411 packages: &[LinkedPackageModule<'_>],
412) -> wasmtime::Result<()> {
413 for package in packages {
414 let name = package.declaration.package_name.as_str();
415 if store.data().git.is_none()
416 && package
417 .module
418 .imports()
419 .any(|import| import.module() == "submilli:git")
420 {
421 wasmtime::bail!(
422 "package `{name}` imports submilli:git; configure the blueprint git block"
423 );
424 }
425 match package.module.name() {
432 Some(declared) if declared == name => {}
433 Some(declared) => wasmtime::bail!(
434 "package `{name}`: its module declares the name `{declared}`, which would give \
435 it that principal's permissions. Rebuild the package."
436 ),
437 None => wasmtime::bail!(
438 "package `{name}`: its module declares no name, so its gated calls cannot be \
439 attributed. Rebuild the package."
440 ),
441 }
442 store
443 .data_mut()
444 .install_type_info(package.type_info.clone());
445 let instance = {
449 let outcome = linker.instantiate_async(&mut *store, package.module).await;
450 name_the_failing_initializer(&mut *store, name, outcome)?
451 };
452 linker.instance(&mut *store, name, instance)?;
453 }
454 Ok(())
455}
456
457fn name_the_failing_initializer(
466 store: &mut Store<StoreData>,
467 package: &str,
468 outcome: wasmtime::Result<Instance>,
469) -> wasmtime::Result<Instance> {
470 let err = match outcome {
471 Ok(instance) => return Ok(instance),
472 Err(err) => err,
473 };
474 let recovered = exec::uncaught_error(store, err);
475 let Some(thrown) = recovered.downcast_ref::<crate::backtrace::ThrownError>() else {
476 return Err(recovered);
477 };
478 Err(wasmtime::Error::new(crate::backtrace::ThrownError {
479 message: format!(
480 "package `{package}` failed to initialize: {}",
481 thrown.message
482 ),
483 backtrace: thrown.backtrace.clone(),
484 denial: thrown.denial.clone(),
485 }))
486}
487
488const NATIVE_STACK_PER_WASM_BYTE: usize = 32;
491const MIN_NATIVE_STACK: usize = 8 * 1024 * 1024;
492
493#[derive(Clone, Debug)]
494pub struct RuntimeConfig {
495 pub fuel: u64,
496 pub max_wasm_stack: usize,
497 pub memory_reservation: u64,
498 pub memory_guard_size: u64,
499 pub memory_reservation_for_growth: u64,
500 pub max_store_bytes: u64,
501 pub timeout: Option<Duration>,
502 pub async_yield_fuel: Option<u64>,
503}
504
505impl Default for RuntimeConfig {
506 fn default() -> Self {
507 Self {
508 fuel: 1_000_000_000_000,
514 max_wasm_stack: 512 * 1024,
515 memory_reservation: 3 * 1024 * 1024,
516 memory_guard_size: 64 * 1024,
517 memory_reservation_for_growth: 16 * 1024 * 1024,
518 max_store_bytes: DEFAULT_MAX_STORE_BYTES,
519 timeout: None,
520 async_yield_fuel: Some(10_000),
525 }
526 }
527}
528
529impl RuntimeConfig {
530 pub fn native_stack_size(&self) -> usize {
552 self.max_wasm_stack
553 .saturating_mul(NATIVE_STACK_PER_WASM_BYTE)
554 .max(MIN_NATIVE_STACK)
555 }
556
557 pub fn engine(&self) -> wasmtime::Result<Engine> {
558 Engine::new(&self.wasmtime_config())
559 }
560
561 pub fn engine_async(&self) -> wasmtime::Result<Engine> {
565 Engine::new(&self.wasmtime_config())
566 }
567
568 pub fn wasmtime_config(&self) -> Config {
569 let mut config = Config::new();
570 config.cranelift_opt_level(OptLevel::None);
581 config.consume_fuel(true);
582 config.max_wasm_stack(self.max_wasm_stack);
583
584 config.epoch_interruption(true);
585
586 config.memory_reservation(self.memory_reservation);
587 config.memory_guard_size(self.memory_guard_size);
588 config.memory_reservation_for_growth(self.memory_reservation_for_growth);
589 config.memory_may_move(true);
590 config.memory_init_cow(true);
591
592 config.gc_heap_reservation(0);
596 config.gc_heap_guard_size(self.memory_guard_size);
597 config.gc_heap_reservation_for_growth(self.memory_reservation_for_growth);
598 config.gc_heap_may_move(true);
599
600 config.wasm_gc(true);
601 config.wasm_function_references(true);
602 config.wasm_exceptions(true);
603
604 config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
605
606 config
607 }
608
609 pub fn store<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
610 let mut store = Store::new(engine, data);
611 store.set_fuel(self.fuel)?;
612 let delta = if self.timeout.is_some() { 1 } else { u64::MAX };
615 store.set_epoch_deadline(delta);
616 store.epoch_deadline_trap();
617 Ok(store)
618 }
619
620 pub fn store_async<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
621 let mut store = self.store(engine, data)?;
622 store.fuel_async_yield_interval(self.async_yield_fuel)?;
623 Ok(store)
624 }
625
626 pub fn arm_timeout(&self, engine: &Engine) -> wasmtime::Result<Option<Watchdog>> {
629 self.timeout
630 .map(|duration| watchdog::arm(engine, duration))
631 .transpose()
632 .map_err(|error| {
633 wasmtime::Error::from(error).context("starting execution timeout watchdog")
634 })
635 }
636
637 pub async fn run(&self, wasm_bytes: &[u8]) -> wasmtime::Result<RunResult> {
650 self.run_with_type_info(wasm_bytes, None).await
651 }
652
653 pub async fn run_compiled(
661 &self,
662 compiled: &crate::compile::CompiledScript,
663 ) -> wasmtime::Result<RunResult> {
664 self.run_with_type_info(&compiled.wasm, Some(compiled.type_info.clone()))
665 .await
666 }
667
668 async fn run_with_type_info(
669 &self,
670 wasm_bytes: &[u8],
671 type_info: Option<TypeInfoTable>,
672 ) -> wasmtime::Result<RunResult> {
673 let buf = Arc::new(Mutex::new(Vec::new()));
674 let mut data =
675 StoreData::with_vfs_and_cap(RunId::generate()?, Vfs::tempdir()?, self.max_store_bytes);
676 data.console = Box::new(ConsoleSink(Arc::clone(&buf)));
677 if let Some(type_info) = type_info {
678 data.install_type_info(type_info);
679 }
680 let engine = self.engine()?;
681 let mut store = self.store_async(&engine, data)?;
682 install_tenant_limits(&mut store);
683 let module = Module::new(&engine, wasm_bytes)?;
684 let mut linker = Linker::<StoreData>::new(&engine);
685 install_runtime_async(&mut linker, &mut store).await?;
686 let _watchdog = self.arm_timeout(&engine)?;
687 let inst = instantiate_program_async(&linker, &mut store, &module).await?;
688 let value = dispatch_main_async(&mut store, &inst).await?;
689 let captured = buf.lock().expect("console buffer lock poisoned").clone();
693 let console = String::from_utf8(captured)
694 .map_err(|e| wasmtime::Error::msg(format!("console output not utf-8: {e}")))?;
695 Ok(RunResult { value, console })
696 }
697}
698
699struct ConsoleSink(Arc<Mutex<Vec<u8>>>);
703
704impl Write for ConsoleSink {
705 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
706 self.0
707 .lock()
708 .expect("console buffer lock poisoned")
709 .write(buf)
710 }
711
712 fn flush(&mut self) -> std::io::Result<()> {
713 Ok(())
714 }
715}
716
717pub(crate) fn read_submilli_string(
719 mut ctx: impl AsContextMut<Data = StoreData>,
720 msg: Rooted<ArrayRef>,
721) -> wasmtime::Result<String> {
722 let units = host::read_code_units(&mut ctx, msg, "string")?;
723 fuel::charge(ctx, fuel::SCAN, units.len() as u64)?;
724 Ok(String::from_utf16_lossy(&units))
725}