1use std::collections::HashMap;
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::time::{SystemTime, UNIX_EPOCH};
15
16use async_trait::async_trait;
17
18use kaish_kernel::ast::Value;
19use kaish_kernel::interpreter::ExecResult;
20use kaish_kernel::tools::ToolSchema;
21use kaish_kernel::vfs::Filesystem;
22use kaish_kernel::{ExecuteOptions, Kernel, KernelConfig};
23
24use crate::traits::{ClientError, ClientResult, KernelClient};
25
26fn generate_blob_id() -> String {
28 static COUNTER: AtomicU64 = AtomicU64::new(0);
29
30 let timestamp = SystemTime::now()
31 .duration_since(UNIX_EPOCH)
32 .map(|d| d.as_nanos())
33 .unwrap_or(0);
34 let count = COUNTER.fetch_add(1, Ordering::SeqCst);
35
36 format!("{:x}-{:x}", timestamp, count)
37}
38
39#[derive(Clone)]
57pub struct EmbeddedClient {
58 kernel: Arc<Kernel>,
59}
60
61impl EmbeddedClient {
62 pub fn new(kernel: Kernel) -> Self {
64 Self {
65 kernel: kernel.into_arc(),
66 }
67 }
68
69 pub fn transient() -> ClientResult<Self> {
71 let kernel = Kernel::new(KernelConfig::transient())
72 .map_err(ClientError::Other)?;
73 Ok(Self::new(kernel))
74 }
75
76 pub fn with_defaults() -> ClientResult<Self> {
78 let kernel = Kernel::new(KernelConfig::default())
79 .map_err(ClientError::Other)?;
80 Ok(Self::new(kernel))
81 }
82
83 pub fn kernel(&self) -> &Kernel {
85 &self.kernel
86 }
87
88 pub async fn execute_streaming(
94 &self,
95 input: &str,
96 on_output: &mut (dyn FnMut(&ExecResult) + Send),
97 ) -> ClientResult<ExecResult> {
98 self.kernel
99 .execute_with_options_streaming(input, ExecuteOptions::default(), on_output)
100 .await
101 .map_err(|e| ClientError::Execution(e.to_string()))
102 }
103
104 pub async fn execute_with_options(
108 &self,
109 input: &str,
110 opts: ExecuteOptions,
111 ) -> ClientResult<ExecResult> {
112 self.kernel
113 .execute_with_options(input, opts)
114 .await
115 .map_err(|e| ClientError::Execution(e.to_string()))
116 }
117
118 pub async fn execute_with_options_streaming(
120 &self,
121 input: &str,
122 opts: ExecuteOptions,
123 on_output: &mut (dyn FnMut(&ExecResult) + Send),
124 ) -> ClientResult<ExecResult> {
125 self.kernel
126 .execute_with_options_streaming(input, opts, on_output)
127 .await
128 .map_err(|e| ClientError::Execution(e.to_string()))
129 }
130}
131
132#[async_trait(?Send)]
133impl KernelClient for EmbeddedClient {
134 async fn execute(&self, input: &str) -> ClientResult<ExecResult> {
135 self.kernel
136 .execute(input)
137 .await
138 .map_err(|e| ClientError::Execution(e.to_string()))
139 }
140
141 async fn execute_with_vars(
142 &self,
143 input: &str,
144 vars: HashMap<String, Value>,
145 ) -> ClientResult<ExecResult> {
146 self.kernel
147 .execute_with_options(input, ExecuteOptions::new().with_vars(vars))
148 .await
149 .map_err(|e| ClientError::Execution(e.to_string()))
150 }
151
152 async fn get_var(&self, name: &str) -> ClientResult<Option<Value>> {
153 Ok(self.kernel.get_var(name).await)
154 }
155
156 async fn set_var(&self, name: &str, value: Value) -> ClientResult<()> {
157 self.kernel.set_var(name, value).await;
158 Ok(())
159 }
160
161 async fn list_vars(&self) -> ClientResult<Vec<(String, Value)>> {
162 Ok(self.kernel.list_vars().await)
163 }
164
165 async fn tool_schemas(&self) -> ClientResult<Vec<ToolSchema>> {
166 Ok(self.kernel.tool_schemas())
167 }
168
169 async fn has_function(&self, name: &str) -> ClientResult<bool> {
170 Ok(self.kernel.has_function(name).await)
171 }
172
173 async fn cancel(&self) -> ClientResult<()> {
174 self.kernel.cancel();
175 Ok(())
176 }
177
178 async fn cwd(&self) -> ClientResult<String> {
179 Ok(self.kernel.cwd().await.to_string_lossy().to_string())
180 }
181
182 async fn set_cwd(&self, path: &str) -> ClientResult<()> {
183 self.kernel.set_cwd(PathBuf::from(path)).await;
184 Ok(())
185 }
186
187 async fn last_result(&self) -> ClientResult<ExecResult> {
188 Ok(self.kernel.last_result().await)
189 }
190
191 async fn reset(&self) -> ClientResult<()> {
192 self.kernel
193 .reset()
194 .await
195 .map_err(ClientError::Other)
196 }
197
198 async fn ping(&self) -> ClientResult<String> {
199 Ok("pong".to_string())
200 }
201
202 async fn shutdown(&self) -> ClientResult<()> {
203 Ok(())
206 }
207
208 async fn read_blob(&self, id: &str) -> ClientResult<Vec<u8>> {
209 let vfs = self.kernel.vfs();
210 let path = PathBuf::from(format!("/v/blobs/{}", id));
211
212 vfs.read(&path)
213 .await
214 .map_err(ClientError::Io)
215 }
216
217 async fn write_blob(&self, content_type: &str, data: &[u8]) -> ClientResult<String> {
218 let vfs = self.kernel.vfs();
219 let id = generate_blob_id();
220 let path = PathBuf::from(format!("/v/blobs/{}", id));
221
222 let parent = Path::new("/v/blobs");
224 if let Err(e) = vfs.mkdir(parent).await {
225 if e.kind() != std::io::ErrorKind::AlreadyExists {
227 tracing::warn!("Failed to create blob directory: {}", e);
228 }
229 }
230
231 tracing::debug!("Creating blob {} with content type {}", id, content_type);
233
234 vfs.write(&path, data)
235 .await
236 .map_err(ClientError::Io)?;
237
238 Ok(id)
239 }
240
241 async fn delete_blob(&self, id: &str) -> ClientResult<bool> {
242 let vfs = self.kernel.vfs();
243 let path = PathBuf::from(format!("/v/blobs/{}", id));
244
245 match vfs.remove(&path).await {
246 Ok(()) => Ok(true),
247 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
248 Err(e) => Err(ClientError::Io(e)),
249 }
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[tokio::test]
258 async fn test_embedded_transient() {
259 let client = EmbeddedClient::transient().expect("failed to create client");
260 let result = client.ping().await.expect("ping failed");
261 assert_eq!(result, "pong");
262 }
263
264 #[tokio::test]
265 async fn test_embedded_execute() {
266 let client = EmbeddedClient::transient().expect("failed to create client");
267 let result = client.execute("echo hello").await.expect("execute failed");
268 assert!(result.ok());
269 assert_eq!(result.text_out().trim(), "hello");
270 }
271
272 #[tokio::test]
273 async fn test_embedded_tool_schemas() {
274 let client = EmbeddedClient::transient().expect("failed to create client");
275 let schemas = client.tool_schemas().await.expect("tool_schemas failed");
276
277 assert!(!schemas.is_empty(), "expected at least one tool schema");
279
280 let echo = schemas
282 .iter()
283 .find(|s| s.name == "echo")
284 .expect("echo builtin should be present");
285 assert!(
288 echo.params
289 .iter()
290 .any(|p| p.matches_flag("n") && p.matches_flag("no-newline")),
291 "echo schema should expose its -n/--no-newline flag for completion"
292 );
293 }
294
295 #[tokio::test]
296 async fn test_embedded_has_function() {
297 let client = EmbeddedClient::transient().expect("failed to create client");
298
299 assert!(
300 !client.has_function("greet").await.expect("has_function failed"),
301 "function should not exist before definition"
302 );
303
304 client
305 .execute("greet() { echo hi; }")
306 .await
307 .expect("defining function failed");
308
309 assert!(
310 client.has_function("greet").await.expect("has_function failed"),
311 "function should exist after definition"
312 );
313 }
314
315 #[tokio::test]
316 async fn test_embedded_cancel_idempotent_when_idle() {
317 let client = EmbeddedClient::transient().expect("failed to create client");
320 client.cancel().await.expect("cancel failed");
321 let result = client.execute("echo ok").await.expect("execute failed");
323 assert!(result.ok());
324 assert_eq!(result.text_out().trim(), "ok");
325 }
326
327 #[tokio::test]
328 async fn test_embedded_variables() {
329 let client = EmbeddedClient::transient().expect("failed to create client");
330
331 client.execute("X=42").await.expect("set failed");
333 let value = client.get_var("X").await.expect("get failed");
334 assert_eq!(value, Some(Value::Int(42)));
335
336 client.set_var("Y", Value::String("hello".into())).await.expect("set_var failed");
338 let value = client.get_var("Y").await.expect("get failed");
339 assert_eq!(value, Some(Value::String("hello".into())));
340
341 let vars = client.list_vars().await.expect("list failed");
343 assert!(vars.iter().any(|(n, _)| n == "X"));
344 assert!(vars.iter().any(|(n, _)| n == "Y"));
345 }
346
347 #[tokio::test]
348 async fn test_embedded_cwd() {
349 let client = EmbeddedClient::transient().expect("failed to create client");
350
351 let cwd = client.cwd().await.expect("cwd failed");
353 let home = std::env::var("HOME").unwrap_or_else(|_| "/".to_string());
354 assert_eq!(cwd, home);
355
356 client.set_cwd("/tmp").await.expect("set_cwd failed");
357 let cwd = client.cwd().await.expect("cwd failed");
358 assert_eq!(cwd, "/tmp");
359 }
360
361 #[tokio::test]
362 async fn test_embedded_reset() {
363 let client = EmbeddedClient::transient().expect("failed to create client");
364
365 client.execute("X=1").await.expect("set failed");
366 assert!(client.get_var("X").await.expect("get failed").is_some());
367
368 client.reset().await.expect("reset failed");
369 assert!(client.get_var("X").await.expect("get failed").is_none());
370 }
371
372 #[tokio::test]
373 async fn test_embedded_last_result() {
374 let client = EmbeddedClient::transient().expect("failed to create client");
375
376 client.execute("echo test").await.expect("execute failed");
377 let last = client.last_result().await.expect("last_result failed");
378 assert!(last.ok());
379 assert_eq!(last.text_out().trim(), "test");
380 }
381
382 #[tokio::test]
383 async fn test_embedded_blob_write_read() {
384 let client = EmbeddedClient::transient().expect("failed to create client");
385
386 let data = b"hello blob world!";
387 let id = client.write_blob("text/plain", data).await.expect("write_blob failed");
388
389 assert!(!id.is_empty(), "blob id should not be empty");
390
391 let read_data = client.read_blob(&id).await.expect("read_blob failed");
392 assert_eq!(read_data, data);
393 }
394
395 #[tokio::test]
396 async fn test_embedded_blob_delete() {
397 let client = EmbeddedClient::transient().expect("failed to create client");
398
399 let data = b"blob to delete";
400 let id = client.write_blob("application/octet-stream", data).await.expect("write_blob failed");
401
402 let read_data = client.read_blob(&id).await.expect("read_blob failed");
404 assert_eq!(read_data, data);
405
406 let deleted = client.delete_blob(&id).await.expect("delete_blob failed");
408 assert!(deleted, "blob should have been deleted");
409
410 let result = client.read_blob(&id).await;
412 assert!(result.is_err(), "blob should not exist after deletion");
413 }
414
415 #[tokio::test]
416 async fn test_embedded_blob_delete_nonexistent() {
417 let client = EmbeddedClient::transient().expect("failed to create client");
418
419 let deleted = client.delete_blob("nonexistent-blob-id").await.expect("delete_blob failed");
420 assert!(!deleted, "deleting nonexistent blob should return false");
421 }
422
423 #[tokio::test]
424 async fn test_embedded_blob_large_data() {
425 let client = EmbeddedClient::transient().expect("failed to create client");
426
427 let data: Vec<u8> = (0..1024 * 1024).map(|i| (i % 256) as u8).collect();
429 let id = client.write_blob("application/octet-stream", &data).await.expect("write_blob failed");
430
431 let read_data = client.read_blob(&id).await.expect("read_blob failed");
432 assert_eq!(read_data.len(), data.len());
433 assert_eq!(read_data, data);
434 }
435}