capsule_core/wasm/commands/
create.rs1use std::path::PathBuf;
2use std::sync::Arc;
3
4use nanoid::nanoid;
5
6use wasmtime::component::{Component, Linker, ResourceTable};
7use wasmtime::{Store, StoreLimitsBuilder};
8use wasmtime_wasi::WasiCtxBuilder;
9use wasmtime_wasi::add_to_linker_async;
10use wasmtime_wasi_http::WasiHttpCtx;
11
12use crate::config::log::{CreateInstanceLog, InstanceState, UpdateInstanceLog};
13use crate::wasm::execution_policy::ExecutionPolicy;
14use crate::wasm::runtime::{Runtime, RuntimeCommand, WasmRuntimeError};
15use crate::wasm::state::{CapsuleAgent, State, capsule};
16
17pub struct CreateInstance {
18 pub policy: ExecutionPolicy,
19 pub args: Vec<String>,
20 pub task_id: String,
21 pub task_name: String,
22 pub agent_name: String,
23 pub agent_version: String,
24 pub wasm_path: PathBuf,
25}
26
27impl CreateInstance {
28 pub fn new(policy: ExecutionPolicy, args: Vec<String>) -> Self {
29 Self {
30 policy,
31 args,
32 task_id: nanoid!(10),
33 task_name: "default".to_string(),
34 agent_name: "default".to_string(),
35 agent_version: "0.0.0".to_string(),
36 wasm_path: PathBuf::from(".capsule/capsule.wasm"),
37 }
38 }
39
40 pub fn task_name(mut self, task_name: impl Into<String>) -> Self {
41 self.task_name = task_name.into();
42 self
43 }
44
45 pub fn agent_name(mut self, agent_name: impl Into<String>) -> Self {
46 self.agent_name = agent_name.into();
47 self
48 }
49
50 pub fn agent_version(mut self, agent_version: impl Into<String>) -> Self {
51 self.agent_version = agent_version.into();
52 self
53 }
54
55 pub fn wasm_path(mut self, wasm_path: PathBuf) -> Self {
56 self.wasm_path = wasm_path;
57 self
58 }
59}
60
61impl RuntimeCommand for CreateInstance {
62 type Output = (Store<State>, CapsuleAgent, String);
63
64 async fn execute(
65 self,
66 runtime: Arc<Runtime>,
67 ) -> Result<(Store<State>, CapsuleAgent, String), WasmRuntimeError> {
68 runtime
69 .log
70 .commit_log(CreateInstanceLog {
71 agent_name: self.agent_name,
72 agent_version: self.agent_version,
73 task_id: self.task_id.clone(),
74 task_name: self.task_name,
75 state: InstanceState::Created,
76 fuel_limit: self.policy.compute.as_fuel(),
77 fuel_consumed: 0,
78 })
79 .await?;
80
81 let mut linker = Linker::<State>::new(&runtime.engine);
82
83 add_to_linker_async(&mut linker)?;
84 wasmtime_wasi_http::add_only_http_to_linker_async(&mut linker)?;
85
86 capsule::host::api::add_to_linker(&mut linker, |state: &mut State| state)?;
87
88 let wasi = WasiCtxBuilder::new()
89 .inherit_stdout()
90 .inherit_stderr()
91 .args(&self.args)
92 .build();
93
94 let mut limits = StoreLimitsBuilder::new();
95
96 if let Some(ram_bytes) = self.policy.ram {
97 limits = limits.memory_size(ram_bytes as usize);
98 }
99
100 let limits = limits.build();
101
102 let state = State {
103 ctx: wasi,
104 http_ctx: WasiHttpCtx::new(),
105 table: ResourceTable::new(),
106 limits,
107 runtime: Some(Arc::clone(&runtime)),
108 };
109
110 let mut store = Store::new(&runtime.engine, state);
111
112 store.set_fuel(self.policy.compute.as_fuel())?;
113
114 store.limiter(|state| state);
115
116 let component = match runtime.get_component().await {
117 Some(c) => c,
118 None => {
119 let cwasm_path = self.wasm_path.with_extension("cwasm");
120
121 let use_cached = if cwasm_path.exists() {
122 let wasm_time = std::fs::metadata(&self.wasm_path)
123 .and_then(|m| m.modified())
124 .ok();
125 let cwasm_time = std::fs::metadata(&cwasm_path)
126 .and_then(|m| m.modified())
127 .ok();
128
129 match (wasm_time, cwasm_time) {
130 (Some(w), Some(c)) => c > w,
131 _ => false,
132 }
133 } else {
134 false
135 };
136
137 let c = if use_cached {
138 unsafe { Component::deserialize_file(&runtime.engine, &cwasm_path)? }
139 } else {
140 let c = Component::from_file(&runtime.engine, &self.wasm_path)?;
141
142 if let Ok(bytes) = c.serialize() {
143 let _ = std::fs::write(&cwasm_path, bytes);
144 }
145 c
146 };
147
148 runtime.set_component(c.clone()).await;
149 c
150 }
151 };
152
153 let instance = match CapsuleAgent::instantiate_async(&mut store, &component, &linker).await
154 {
155 Ok(instance) => instance,
156 Err(e) => {
157 runtime
158 .log
159 .update_log(UpdateInstanceLog {
160 task_id: self.task_id,
161 state: InstanceState::Failed,
162 fuel_consumed: 0,
163 })
164 .await?;
165 return Err(WasmRuntimeError::WasmtimeError(e));
166 }
167 };
168
169 Ok((store, instance, self.task_id))
170 }
171}