capsule_core/wasm/commands/
create.rs

1use 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}