Skip to main content

soma_provider_adapters/wasm/
direct.rs

1use super::*;
2
3/// A compiled component retained independently of the global deduplication cache.
4#[derive(Clone)]
5pub struct PreparedComponentArtifact {
6    runtime: Arc<WasmRuntime>,
7    artifact: Arc<WasmArtifact>,
8    componentize: bool,
9}
10
11/// Validate and invoke a component artifact directly without network access.
12pub fn invoke_authorized_component_artifact(
13    path: &std::path::Path,
14    input: &Value,
15    capabilities: &soma_provider_core::HostCapabilities,
16    context: &soma_provider_core::ProviderInvocationContext,
17) -> Result<Value, String> {
18    if capabilities
19        .network
20        .as_ref()
21        .is_some_and(|network| network.enabled)
22    {
23        return Err(
24            "synchronous component conformance does not permit network host calls".to_owned(),
25        );
26    }
27    let artifact_bytes = read_artifact(path)?;
28    let componentize = is_componentize_artifact(&artifact_bytes);
29    let limits = conformance_limits(componentize);
30    let bytes = conformance_input(input, limits)?;
31    let runtime = shared_wasm_runtime()?;
32    let artifact = runtime.artifact(
33        &artifact_bytes,
34        Instant::now() + artifact_compile_timeout(&artifact_bytes),
35    )?;
36    let deadline = Instant::now() + Duration::from_millis(limits.timeout_ms);
37    let output = runtime.run_artifact(
38        artifact,
39        WasmInvocation {
40            input: &bytes,
41            limits,
42            capabilities,
43            context,
44            resolved_hosts: BTreeMap::new(),
45            deadline,
46        },
47    )?;
48    serde_json::from_slice(&output).map_err(|error| error.to_string())
49}
50
51/// Invoke a component artifact under the caller's real authority and a single
52/// absolute deadline.
53pub async fn invoke_component_artifact_async(
54    path: &std::path::Path,
55    input: &Value,
56    capabilities: &soma_provider_core::HostCapabilities,
57    context: &soma_provider_core::ProviderInvocationContext,
58) -> Result<Value, String> {
59    let bytes = read_artifact(path)?;
60    let componentize = is_componentize_artifact(&bytes);
61    let prepared = prepare_component_artifact_bytes_before(
62        &bytes,
63        Instant::now() + artifact_compile_timeout(&bytes),
64    )?;
65    let limits = conformance_limits(componentize);
66    invoke_prepared_component_artifact_before_async(
67        &prepared,
68        input,
69        capabilities,
70        context,
71        Instant::now() + Duration::from_millis(limits.timeout_ms),
72    )
73    .await
74}
75
76/// Invoke a prepared component using the caller's absolute operation deadline.
77pub async fn invoke_component_artifact_before_async(
78    path: &std::path::Path,
79    input: &Value,
80    capabilities: &soma_provider_core::HostCapabilities,
81    context: &soma_provider_core::ProviderInvocationContext,
82    deadline: Instant,
83) -> Result<Value, String> {
84    let prepared = prepare_component_artifact_before(path, deadline)?;
85    invoke_prepared_component_artifact_before_async(
86        &prepared,
87        input,
88        capabilities,
89        context,
90        deadline,
91    )
92    .await
93}
94
95/// Compile and retain a component artifact through a later invocation.
96pub fn prepare_component_artifact_before(
97    path: &std::path::Path,
98    deadline: Instant,
99) -> Result<PreparedComponentArtifact, String> {
100    let bytes = read_artifact(path)?;
101    prepare_component_artifact_bytes_before(&bytes, deadline)
102}
103
104fn prepare_component_artifact_bytes_before(
105    bytes: &[u8],
106    deadline: Instant,
107) -> Result<PreparedComponentArtifact, String> {
108    let componentize = is_componentize_artifact(bytes);
109    let runtime = shared_wasm_runtime()?;
110    let artifact = runtime.artifact(bytes, deadline)?;
111    if !matches!(artifact.as_ref(), WasmArtifact::Component(_)) {
112        return Err("artifact is core Wasm, not a component".to_owned());
113    }
114    Ok(PreparedComponentArtifact {
115        runtime,
116        artifact,
117        componentize,
118    })
119}
120
121/// Invoke an already-compiled component under one absolute operation deadline.
122pub async fn invoke_prepared_component_artifact_before_async(
123    prepared: &PreparedComponentArtifact,
124    input: &Value,
125    capabilities: &soma_provider_core::HostCapabilities,
126    context: &soma_provider_core::ProviderInvocationContext,
127    deadline: Instant,
128) -> Result<Value, String> {
129    let limits = conformance_limits(prepared.componentize);
130    let bytes = conformance_input(input, limits)?;
131    let resolved_hosts = resolve_component_hosts(capabilities, deadline).await?;
132    let runtime = prepared.runtime.clone();
133    let artifact = prepared.artifact.clone();
134    let capabilities = capabilities.clone();
135    let context = context.clone();
136    let task = tokio::task::spawn_blocking(move || {
137        runtime.run_artifact(
138            artifact,
139            WasmInvocation {
140                input: &bytes,
141                limits,
142                capabilities: &capabilities,
143                context: &context,
144                resolved_hosts,
145                deadline,
146            },
147        )
148    });
149    let remaining = deadline
150        .checked_duration_since(Instant::now())
151        .unwrap_or(Duration::ZERO);
152    let task_result = timeout(remaining, task)
153        .await
154        .map_err(|_| "component conformance invocation timed out".to_owned())?
155        .map_err(|error| format!("component conformance task failed: {error}"))?;
156    if Instant::now() >= deadline {
157        return Err("component conformance invocation timed out".to_owned());
158    }
159    let output = task_result?;
160    serde_json::from_slice(&output).map_err(|error| error.to_string())
161}
162
163/// Validate that `path` contains a component-model artifact.
164pub fn verify_component_artifact(path: &std::path::Path) -> Result<(), String> {
165    let bytes = read_artifact(path)?;
166    let deadline = Instant::now() + artifact_compile_timeout(&bytes);
167    let prepared = prepare_component_artifact_bytes_before(&bytes, deadline)?;
168    prepared
169        .runtime
170        .verify_prepared_component(&prepared.artifact, prepared.componentize, deadline)
171}
172
173/// Validate a component-model artifact within an existing absolute deadline.
174pub fn verify_component_artifact_before(
175    path: &std::path::Path,
176    deadline: Instant,
177) -> Result<(), String> {
178    let prepared = prepare_component_artifact_before(path, deadline)?;
179    prepared
180        .runtime
181        .verify_prepared_component(&prepared.artifact, prepared.componentize, deadline)
182}
183
184fn conformance_limits(componentize: bool) -> WasmRuntimeLimits {
185    WasmRuntimeLimits {
186        timeout_ms: 5_000,
187        max_input_bytes: 64 * 1024,
188        max_output_bytes: 256 * 1024,
189        fuel: 1_000_000,
190        max_memory_bytes: 64 * 1024 * 1024,
191        max_table_elements: 10_000,
192        max_instances: 16,
193    }
194    .with_componentize_minimums(componentize)
195}
196
197fn conformance_input(input: &Value, limits: WasmRuntimeLimits) -> Result<Vec<u8>, String> {
198    let bytes = serde_json::to_vec(input).map_err(|error| error.to_string())?;
199    if bytes.len() > limits.max_input_bytes {
200        return Err(format!(
201            "component conformance input exceeds {} bytes",
202            limits.max_input_bytes
203        ));
204    }
205    Ok(bytes)
206}