1pub mod agents;
4pub(crate) mod array_storage;
5pub mod blocking;
6pub mod call_log;
7pub mod decision;
8pub mod disk_quota;
9pub mod embedding;
10pub mod exec;
11pub mod fs;
12pub mod fuel;
13pub mod gc_singleton;
14pub mod host;
15pub mod intrinsic_types;
16pub mod json;
17pub(crate) mod json_text;
18pub mod limits;
19pub mod llm;
20pub mod mcp;
21pub mod metrics;
22pub mod number;
23pub mod prelude;
24pub mod secrets;
25pub mod security;
26pub mod session_kv;
27pub mod skills;
28pub mod token_ledger;
29pub mod tools;
30pub mod vfs;
31pub mod watchdog;
32
33pub use agents::{
34 AGENTS_MODULE_NAME, AgentCallError, AgentInfo, AgentOutcome, AgentProvider, AgentRequest,
35 AgentUsage,
36};
37pub use call_log::{BodyCopy, CallOutcome, CallRecord, ModelUsage, PayloadRecord};
38pub use decision::{
39 CallOrigin, CallSite, CallTicket, ConsoleLine, DecisionAction, DecisionCause,
40 DecisionExplanation, DecisionLog, DecisionLogConfig, DecisionLogOutput, DecisionRecord,
41 DecisionRecorder, EntryPath, FailureReasonRecord, FailureRecord, NearMissRecord, PackageLine,
42 RecordObserver, RuleCitation, SourceLine,
43};
44pub use disk_quota::{DiskQuota, Holder, OpenFileGuard, QuotaCharge, QuotaExceeded};
45pub use embedding::{
46 DEFAULT_MAX_EMBEDDING_REQUESTS, DEFAULT_MAX_EXECUTION_EMBEDDING_TOKENS, EMBEDDING_MODULE_NAME,
47 EmbeddingBatch, EmbeddingBoundKind, EmbeddingError, EmbeddingFailureReason, EmbeddingLimitKind,
48 EmbeddingLimits, EmbeddingMalformedReason, EmbeddingModel, EmbeddingProvider,
49 EmbeddingShapeError, EmbeddingTokenBudget, Purpose, SubBatchSettlement,
50 estimate_embedding_tokens,
51};
52pub use exec::{RunResult, dispatch_main_async, instantiate_program_async};
53pub use host::{
54 INTERNAL_MODULE_NAME, NUMBER_MODULE_NAME, host_package_declarations,
55 install_async as install_runtime_async, install_async_for as install_runtime_async_for,
56 install_host_functions as install_runtime_host_functions,
57 install_host_functions_for as install_runtime_host_functions_for,
58 install_store_bound as install_runtime_store_bound, internal_host_package_declarations,
59 stdlib_package_declarations,
60};
61pub use json::JSON_MODULE_NAME;
62pub use limits::{
63 DEFAULT_MAX_STORE_BYTES, MemoryCapExceeded, MemoryExhausted, TenantLimits,
64 install_tenant_limits, is_memory_exhausted,
65};
66pub use llm::{
67 DEFAULT_MAX_ALL_EXECUTIONS_TOKENS, DEFAULT_MAX_EXECUTION_TOKENS, ExecutionTokenBudget,
68 FailureReason, LLM_MODULE_NAME, LlmCallError, LlmFailure, LlmLimitKind, LlmLimits, LlmModel,
69 LlmOutcome, LlmProvider, PromptBoundKind, SharedTokenBudget,
70};
71pub use mcp::{
72 MCP_MODULE_NAME, McpCallError, McpOutcome, McpResponse, McpTransport, install_mcp_async,
73 mcp_call_package_declaration,
74};
75pub use metrics::{HttpMetric, MetricsSink, NoopMetricsSink};
76pub use prelude::bigint::ops::BIGINT_MODULE_NAME;
77pub use prelude::temporal::shared::TEMPORAL_MODULE_NAME;
78pub use secrets::{NoopSecretProvider, SecretProvider};
79pub use security::{AllowAllCheck, AuditDecision, CheckOutcome, SecurityCheck};
80pub use session_kv::{
81 InMemorySessionKv, SessionKvEntry, SessionKvError, SessionKvLimitKind, SessionKvLimits,
82 SessionKvPage, SessionKvStore, SharedKvBudget,
83};
84pub use skills::{SKILLS_MODULE_NAME, Skill, SkillError, SkillInfo, SkillProvider};
85pub use tools::{
86 TOOLS_CAPABILITY, TOOLS_MODULE_NAME, TOOLS_PACKAGE, ToolCallError, ToolProvider,
87 install_tools_async, tools_call_package_declaration,
88};
89pub use vfs::{
90 Access, CopyDirError, MAX_MEASURED_DEPTH, MountError, MountSpec, Vfs, VfsMode, copy_host_dir,
91 copy_host_subdir, measure_dir, measure_host_dir, measure_host_dir_skipping_vanished,
92 measure_host_subdir_skipping_vanished, open_host_subdir, regular_files,
93};
94pub use watchdog::Watchdog;
95
96pub use crate::stdlib::http::{
97 AuthProxy, AuthProxyError, HttpClient, HttpError, HttpRequest, HttpResponse, NetworkPolicy,
98 NoopAuthProxy, ReqwestHttpClient,
99};
100
101use std::cell::RefCell;
102use std::io::Write;
103use std::sync::{Arc, Mutex};
104use std::time::Duration;
105
106use wasmtime::{
107 ArrayRef, AsContextMut, Config, Engine, Instance, Linker, Module, OptLevel, Rooted, Store,
108 WasmBacktraceDetails,
109};
110
111use crate::{PackageDeclaration, TypeInfoTable};
112
113#[derive(Debug, Clone)]
117pub struct VfsInfo {
118 pub mode: VfsMode,
119 pub size_limit: Option<u64>,
120}
121
122pub struct StoreData {
123 #[cfg(test)]
124 pub(crate) array_growth: array_storage::GrowthStats,
125 pub blocking_work: blocking::BlockingWork,
128 pub git: Option<crate::stdlib::git::GitConfig>,
129 pub(crate) git_history: Option<Arc<crate::stdlib::git::log_cache::Cache>>,
130 pub console: Box<dyn Write + Send>,
131 pub vfs: Vfs,
132 pub vfs_info: VfsInfo,
133 pub security_check: Arc<dyn SecurityCheck>,
134 pub fs_max_read_size: u64,
135 pub http_client: Arc<dyn HttpClient>,
136 pub http_max_response_size: u64,
137 pub http_max_download_timeout_ms: u64,
139 pub auth_proxy: Arc<dyn AuthProxy>,
140 pub secret_provider: Arc<dyn SecretProvider>,
141 pub mcp_transport: Option<Arc<dyn McpTransport>>,
145 pub tool_provider: Option<Arc<dyn ToolProvider>>,
149 pub clock_paused: std::time::Duration,
153 pub llm_provider: Option<Arc<dyn LlmProvider>>,
159 pub llm_budget: Option<Arc<ExecutionTokenBudget>>,
166 pub embedding_provider: Option<Arc<dyn EmbeddingProvider>>,
170 pub embedding_budget: Option<Arc<EmbeddingTokenBudget>>,
173 pub session_kv: Option<Arc<dyn SessionKvStore>>,
178 pub agent_provider: Option<Arc<dyn AgentProvider>>,
182 pub skill_provider: Option<Arc<dyn SkillProvider>>,
186 pub metrics: Arc<dyn metrics::MetricsSink>,
189 pub tenant_limits: TenantLimits,
190 pub host_fuel: u64,
193 pub(crate) next_identity_hash: u64,
194 #[cfg(test)]
195 pub(crate) collection_index_reads: u64,
196 #[cfg(test)]
197 pub(crate) object_index_probes: u64,
198 pub host_fuel_pending: u64,
201 pub host_fuel_applied_at: Option<u64>,
204 pub test_labels: RefCell<Vec<String>>,
209 pub host_abi: Option<crate::runtime::host::HostAbi>,
214 pub(crate) intrinsic_types: Option<Arc<crate::runtime::intrinsic_types::IntrinsicTypes>>,
217 pub(crate) undefined_value: Option<wasmtime::Global>,
219 pub(crate) closure_receiver_type: Option<wasmtime::StructType>,
222 pub(crate) call_metadata_type: Option<wasmtime::StructType>,
225 pub(crate) iterator_functions: [Option<wasmtime::Func>; 10],
226 pub(crate) iterator_constants: [Option<wasmtime::Global>; 6],
227 pub(crate) static_values: std::collections::BTreeMap<Vec<u16>, wasmtime::Global>,
230 pub(crate) parameter_cache: prelude::arguments::ParameterCache,
231 pub(crate) regex_input: Option<prelude::regex::input::InputCache>,
232 pub type_info: std::collections::BTreeMap<String, TypeInfoTable>,
234 pub vtable_walk_depth: u32,
237 pub(crate) vtable_walk_nodes: u32,
240 pub(crate) settling_host_result: bool,
242 pub(crate) thrown_denials: host::ThrownDenials,
244}
245
246pub(crate) const MAX_VTABLE_WALK_DEPTH: u32 = 128;
255
256pub(crate) const MAX_STRUCTURAL_WALK_NODES: u32 = 100_000;
258
259pub const DEFAULT_FS_MAX_READ_SIZE: u64 = 50 * 1024 * 1024;
260
261pub const DEFAULT_HTTP_MAX_RESPONSE_SIZE: u64 = 50 * 1024 * 1024;
262
263#[derive(Clone, Copy)]
264pub struct LinkedPackageModule<'a> {
265 pub module: &'a Module,
266 pub declaration: &'a PackageDeclaration,
267 pub type_info: &'a TypeInfoTable,
268}
269
270impl StoreData {
271 pub fn with_vfs(vfs: Vfs) -> Self {
272 Self::with_vfs_and_cap(vfs, DEFAULT_MAX_STORE_BYTES)
273 }
274
275 pub fn with_vfs_and_cap(vfs: Vfs, max_store_bytes: u64) -> Self {
276 let vfs_info = VfsInfo {
277 mode: vfs.mode(),
278 size_limit: None,
279 };
280 Self {
281 #[cfg(test)]
282 array_growth: array_storage::GrowthStats::default(),
283 console: Box::new(std::io::stderr()),
284 vfs,
285 vfs_info,
286 security_check: security::default_check(),
287 git: None,
288 git_history: None,
289 blocking_work: blocking::BlockingWork::default(),
290 fs_max_read_size: DEFAULT_FS_MAX_READ_SIZE,
291 http_client: crate::stdlib::http::default_http_client(),
292 http_max_response_size: DEFAULT_HTTP_MAX_RESPONSE_SIZE,
293 http_max_download_timeout_ms: 60_000,
294 auth_proxy: crate::stdlib::http::default_auth_proxy(),
295 secret_provider: Arc::new(secrets::NoopSecretProvider),
296 mcp_transport: None,
297 tool_provider: None,
298 clock_paused: std::time::Duration::ZERO,
299 llm_provider: None,
300 llm_budget: None,
301 embedding_provider: None,
302 embedding_budget: None,
303 session_kv: None,
304 agent_provider: None,
305 skill_provider: None,
306 metrics: Arc::new(metrics::NoopMetricsSink),
307 tenant_limits: TenantLimits::new(max_store_bytes),
308 host_fuel: 0,
309 #[cfg(test)]
310 collection_index_reads: 0,
311 #[cfg(test)]
312 object_index_probes: 0,
313 next_identity_hash: 0,
314 host_fuel_pending: 0,
315 host_fuel_applied_at: None,
316 test_labels: RefCell::new(Vec::new()),
317 host_abi: None,
318 intrinsic_types: None,
319 undefined_value: None,
320 closure_receiver_type: None,
321 call_metadata_type: None,
322 iterator_functions: [None; 10],
323 iterator_constants: [None; 6],
324 static_values: std::collections::BTreeMap::new(),
325 parameter_cache: Default::default(),
326 regex_input: None,
327 type_info: std::collections::BTreeMap::new(),
328 vtable_walk_depth: 0,
329 vtable_walk_nodes: 0,
330 settling_host_result: false,
331 thrown_denials: host::ThrownDenials::default(),
332 }
333 }
334
335 pub fn install_type_info(&mut self, table: TypeInfoTable) {
336 self.type_info.insert(table.package_name.clone(), table);
337 }
338
339 pub fn with_tempdir() -> std::io::Result<Self> {
340 Ok(Self::with_vfs(Vfs::tempdir()?))
341 }
342}
343
344pub async fn install_package_modules_async(
349 linker: &mut Linker<StoreData>,
350 store: &mut Store<StoreData>,
351 packages: &[LinkedPackageModule<'_>],
352) -> wasmtime::Result<()> {
353 for package in packages {
354 let name = package.declaration.package_name.as_str();
355 if store.data().git.is_none()
356 && package
357 .module
358 .imports()
359 .any(|import| import.module() == "submilli:git")
360 {
361 wasmtime::bail!(
362 "package `{name}` imports submilli:git; configure the blueprint git block"
363 );
364 }
365 match package.module.name() {
372 Some(declared) if declared == name => {}
373 Some(declared) => wasmtime::bail!(
374 "package `{name}`: its module declares the name `{declared}`, which would give \
375 it that principal's permissions. Rebuild the package."
376 ),
377 None => wasmtime::bail!(
378 "package `{name}`: its module declares no name, so its gated calls cannot be \
379 attributed. Rebuild the package."
380 ),
381 }
382 store
383 .data_mut()
384 .install_type_info(package.type_info.clone());
385 let instance = {
389 let outcome = linker.instantiate_async(&mut *store, package.module).await;
390 name_the_failing_initializer(&mut *store, name, outcome)?
391 };
392 linker.instance(&mut *store, name, instance)?;
393 }
394 Ok(())
395}
396
397fn name_the_failing_initializer(
406 store: &mut Store<StoreData>,
407 package: &str,
408 outcome: wasmtime::Result<Instance>,
409) -> wasmtime::Result<Instance> {
410 let err = match outcome {
411 Ok(instance) => return Ok(instance),
412 Err(err) => err,
413 };
414 let recovered = exec::uncaught_error(store, err);
415 let Some(thrown) = recovered.downcast_ref::<crate::backtrace::ThrownError>() else {
416 return Err(recovered);
417 };
418 Err(wasmtime::Error::new(crate::backtrace::ThrownError {
419 message: format!(
420 "package `{package}` failed to initialize: {}",
421 thrown.message
422 ),
423 backtrace: thrown.backtrace.clone(),
424 denial: thrown.denial.clone(),
425 }))
426}
427
428const NATIVE_STACK_PER_WASM_BYTE: usize = 32;
431const MIN_NATIVE_STACK: usize = 8 * 1024 * 1024;
432
433#[derive(Clone, Debug)]
434pub struct RuntimeConfig {
435 pub fuel: u64,
436 pub max_wasm_stack: usize,
437 pub memory_reservation: u64,
438 pub memory_guard_size: u64,
439 pub memory_reservation_for_growth: u64,
440 pub max_store_bytes: u64,
441 pub timeout: Option<Duration>,
442 pub async_yield_fuel: Option<u64>,
443}
444
445impl Default for RuntimeConfig {
446 fn default() -> Self {
447 Self {
448 fuel: 1_000_000_000_000,
454 max_wasm_stack: 512 * 1024,
455 memory_reservation: 3 * 1024 * 1024,
456 memory_guard_size: 64 * 1024,
457 memory_reservation_for_growth: 16 * 1024 * 1024,
458 max_store_bytes: DEFAULT_MAX_STORE_BYTES,
459 timeout: None,
460 async_yield_fuel: Some(10_000),
465 }
466 }
467}
468
469impl RuntimeConfig {
470 pub fn native_stack_size(&self) -> usize {
492 self.max_wasm_stack
493 .saturating_mul(NATIVE_STACK_PER_WASM_BYTE)
494 .max(MIN_NATIVE_STACK)
495 }
496
497 pub fn engine(&self) -> wasmtime::Result<Engine> {
498 Engine::new(&self.wasmtime_config())
499 }
500
501 pub fn engine_async(&self) -> wasmtime::Result<Engine> {
505 Engine::new(&self.wasmtime_config())
506 }
507
508 pub fn wasmtime_config(&self) -> Config {
509 let mut config = Config::new();
510 config.cranelift_opt_level(OptLevel::None);
521 config.consume_fuel(true);
522 config.max_wasm_stack(self.max_wasm_stack);
523
524 config.epoch_interruption(true);
525
526 config.memory_reservation(self.memory_reservation);
527 config.memory_guard_size(self.memory_guard_size);
528 config.memory_reservation_for_growth(self.memory_reservation_for_growth);
529 config.memory_may_move(true);
530 config.memory_init_cow(true);
531
532 config.gc_heap_reservation(0);
536 config.gc_heap_guard_size(self.memory_guard_size);
537 config.gc_heap_reservation_for_growth(self.memory_reservation_for_growth);
538 config.gc_heap_may_move(true);
539
540 config.wasm_gc(true);
541 config.wasm_function_references(true);
542 config.wasm_exceptions(true);
543
544 config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
545
546 config
547 }
548
549 pub fn store<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
550 let mut store = Store::new(engine, data);
551 store.set_fuel(self.fuel)?;
552 let delta = if self.timeout.is_some() { 1 } else { u64::MAX };
555 store.set_epoch_deadline(delta);
556 store.epoch_deadline_trap();
557 Ok(store)
558 }
559
560 pub fn store_async<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
561 let mut store = self.store(engine, data)?;
562 store.fuel_async_yield_interval(self.async_yield_fuel)?;
563 Ok(store)
564 }
565
566 pub fn arm_timeout(&self, engine: &Engine) -> wasmtime::Result<Option<Watchdog>> {
569 self.timeout
570 .map(|duration| watchdog::arm(engine, duration))
571 .transpose()
572 .map_err(|error| {
573 wasmtime::Error::from(error).context("starting execution timeout watchdog")
574 })
575 }
576
577 pub async fn run(&self, wasm_bytes: &[u8]) -> wasmtime::Result<RunResult> {
590 self.run_with_type_info(wasm_bytes, None).await
591 }
592
593 pub async fn run_compiled(
601 &self,
602 compiled: &crate::compile::CompiledScript,
603 ) -> wasmtime::Result<RunResult> {
604 self.run_with_type_info(&compiled.wasm, Some(compiled.type_info.clone()))
605 .await
606 }
607
608 async fn run_with_type_info(
609 &self,
610 wasm_bytes: &[u8],
611 type_info: Option<TypeInfoTable>,
612 ) -> wasmtime::Result<RunResult> {
613 let buf = Arc::new(Mutex::new(Vec::new()));
614 let mut data = StoreData::with_vfs_and_cap(Vfs::tempdir()?, self.max_store_bytes);
615 data.console = Box::new(ConsoleSink(Arc::clone(&buf)));
616 if let Some(type_info) = type_info {
617 data.install_type_info(type_info);
618 }
619 let engine = self.engine()?;
620 let mut store = self.store_async(&engine, data)?;
621 install_tenant_limits(&mut store);
622 let module = Module::new(&engine, wasm_bytes)?;
623 let mut linker = Linker::<StoreData>::new(&engine);
624 install_runtime_async(&mut linker, &mut store).await?;
625 let _watchdog = self.arm_timeout(&engine)?;
626 let inst = instantiate_program_async(&linker, &mut store, &module).await?;
627 let value = dispatch_main_async(&mut store, &inst).await?;
628 let captured = buf.lock().expect("console buffer lock poisoned").clone();
632 let console = String::from_utf8(captured)
633 .map_err(|e| wasmtime::Error::msg(format!("console output not utf-8: {e}")))?;
634 Ok(RunResult { value, console })
635 }
636}
637
638struct ConsoleSink(Arc<Mutex<Vec<u8>>>);
642
643impl Write for ConsoleSink {
644 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
645 self.0
646 .lock()
647 .expect("console buffer lock poisoned")
648 .write(buf)
649 }
650
651 fn flush(&mut self) -> std::io::Result<()> {
652 Ok(())
653 }
654}
655
656pub(crate) fn read_submilli_string(
658 mut ctx: impl AsContextMut<Data = StoreData>,
659 msg: Rooted<ArrayRef>,
660) -> wasmtime::Result<String> {
661 let units = host::read_code_units(&mut ctx, msg, "string")?;
662 fuel::charge(ctx, fuel::SCAN, units.len() as u64)?;
663 Ok(String::from_utf16_lossy(&units))
664}