diff --git a/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/generic_chain/generic_inference_chain.rs b/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/generic_chain/generic_inference_chain.rs index c86c1ab6d..26669958f 100644 --- a/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/generic_chain/generic_inference_chain.rs +++ b/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/generic_chain/generic_inference_chain.rs @@ -11,6 +11,7 @@ use crate::managers::sheet_manager::SheetManager; use crate::managers::tool_router::{ToolCallFunctionResponse, ToolRouter}; use crate::network::agent_payments_manager::external_agent_offerings_manager::ExtAgentOfferingsManager; use crate::network::agent_payments_manager::my_agent_offerings_manager::MyAgentOfferingsManager; +use crate::utils::environment::{fetch_node_environment, NodeEnvironment}; use async_trait::async_trait; use shinkai_message_primitives::schemas::inbox_name::InboxName; use shinkai_message_primitives::schemas::job::{Job, JobLike}; @@ -83,6 +84,7 @@ impl InferenceChain for GenericInferenceChain { self.context.ext_agent_payments_manager.clone(), // self.context.sqlite_logger.clone(), self.context.llm_stopper.clone(), + fetch_node_environment(), ) .await?; Ok(response) @@ -122,6 +124,7 @@ impl GenericInferenceChain { ext_agent_payments_manager: Option>>, // sqlite_logger: Option>, llm_stopper: Arc, + node_env: NodeEnvironment, ) -> Result { shinkai_log( ShinkaiLogOption::JobExecution, @@ -327,6 +330,8 @@ impl GenericInferenceChain { Some(full_job.step_history.clone()), tools.clone(), None, + full_job.job_id.clone(), + node_env.clone(), ); let mut iteration_count = 0; @@ -394,7 +399,10 @@ impl GenericInferenceChain { // 6) Call workflow or tooling // Find the ShinkaiTool that has a tool with the function name - let shinkai_tool = tools.iter().find(|tool| tool.name() == function_call.name || tool.tool_router_key() == function_call.tool_router_key.clone().unwrap_or_default()); + let shinkai_tool = tools.iter().find(|tool| { + tool.name() == function_call.name + || tool.tool_router_key() == function_call.tool_router_key.clone().unwrap_or_default() + }); if shinkai_tool.is_none() { eprintln!("Function not found: {}", function_call.name); return Err(LLMProviderError::FunctionNotFound(function_call.name.clone())); @@ -443,6 +451,8 @@ impl GenericInferenceChain { Some(full_job.step_history.clone()), tools.clone(), Some(function_response), + full_job.job_id.clone(), + node_env.clone(), ); } else { // No more function calls required, return the final response diff --git a/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/generic_chain/generic_prompts.rs b/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/generic_chain/generic_prompts.rs index 72c68df67..e35b480de 100644 --- a/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/generic_chain/generic_prompts.rs +++ b/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/generic_chain/generic_prompts.rs @@ -2,6 +2,9 @@ use std::collections::HashMap; use crate::llm_provider::execution::prompts::general_prompts::JobPromptGenerator; use crate::managers::tool_router::ToolCallFunctionResponse; +use crate::network::v2_api::api_v2_commands_app_files::get_app_folder_path; +use crate::network::Node; +use crate::utils::environment::NodeEnvironment; use serde_json::json; use shinkai_message_primitives::schemas::job::JobStepResult; use shinkai_message_primitives::schemas::prompts::Prompt; @@ -23,6 +26,8 @@ impl JobPromptGenerator { job_step_history: Option>, tools: Vec, function_call: Option, + job_id: String, + node_env: NodeEnvironment, ) -> Prompt { let mut prompt = Prompt::new(); @@ -52,6 +57,16 @@ impl JobPromptGenerator { priority = priority.saturating_sub(1); } } + + let folder = get_app_folder_path(node_env, job_id); + let current_files = Node::v2_api_list_app_files_internal(folder.clone(), true); + if let Ok(current_files) = current_files { + prompt.add_content( + format!("Current files: {}", current_files.join(", ")), + SubPromptType::ExtraContext, + 97, + ); + } } // Parses the retrieved nodes as individual sub-prompts, to support priority pruning diff --git a/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/sheet_ui_chain/sheet_ui_inference_chain.rs b/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/sheet_ui_chain/sheet_ui_inference_chain.rs index 847d90ab0..9cb967969 100644 --- a/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/sheet_ui_chain/sheet_ui_inference_chain.rs +++ b/shinkai-bin/shinkai-node/src/llm_provider/execution/chains/sheet_ui_chain/sheet_ui_inference_chain.rs @@ -12,6 +12,7 @@ use crate::managers::sheet_manager::SheetManager; use crate::managers::tool_router::{ToolCallFunctionResponse, ToolRouter}; use crate::network::agent_payments_manager::external_agent_offerings_manager::ExtAgentOfferingsManager; use crate::network::agent_payments_manager::my_agent_offerings_manager::MyAgentOfferingsManager; +use crate::utils::environment::{fetch_node_environment, NodeEnvironment}; use async_trait::async_trait; use shinkai_message_primitives::schemas::inbox_name::InboxName; use shinkai_message_primitives::schemas::job::{Job, JobLike}; @@ -79,6 +80,7 @@ impl InferenceChain for SheetUIInferenceChain { self.context.ext_agent_payments_manager.clone(), // self.context.sqlite_logger.clone(), self.context.llm_stopper.clone(), + fetch_node_environment(), ) .await?; let job_execution_context = self.context.execution_context.clone(); @@ -123,6 +125,7 @@ impl SheetUIInferenceChain { ext_agent_payments_manager: Option>>, // sqlite_logger: Option>, llm_stopper: Arc, + node_env: NodeEnvironment, ) -> Result { shinkai_log( ShinkaiLogOption::JobExecution, @@ -283,6 +286,8 @@ impl SheetUIInferenceChain { Some(full_job.step_history.clone()), tools.clone(), None, + full_job.job_id.clone(), + node_env.clone(), ); let mut iteration_count = 0; @@ -419,6 +424,8 @@ impl SheetUIInferenceChain { Some(full_job.step_history.clone()), tools.clone(), Some(function_response), + full_job.job_id.clone(), + node_env.clone(), ); } else { // No more function calls required, return the final response diff --git a/shinkai-bin/shinkai-node/src/managers/tool_router.rs b/shinkai-bin/shinkai-node/src/managers/tool_router.rs index 97dc978ce..ad631a5f9 100644 --- a/shinkai-bin/shinkai-node/src/managers/tool_router.rs +++ b/shinkai-bin/shinkai-node/src/managers/tool_router.rs @@ -5,6 +5,7 @@ use std::time::Instant; use crate::llm_provider::error::LLMProviderError; use crate::llm_provider::execution::chains::inference_chain_trait::{FunctionCall, InferenceChainContextTrait}; +use crate::network::v2_api::api_v2_commands_app_files::get_app_folder_path; use crate::network::Node; use crate::tools::tool_definitions::definition_generation::{generate_tool_definitions, get_rust_tools}; use crate::tools::tool_execution::execution_header_generator::generate_execution_environment; @@ -621,6 +622,14 @@ async def run(c: CONFIG, p: INPUTS) -> OUTPUT: .await .map_err(|e| ToolError::ExecutionError(e.to_string()))?; + let folder = get_app_folder_path(node_env.clone(), context.full_job().job_id().to_string()); + let mounts = Node::v2_api_list_app_files_internal(folder.clone(), true); + if let Err(e) = mounts { + eprintln!("Failed to list app files: {:?}", e); + return Err(LLMProviderError::FunctionExecutionError(format!("{:?}", e))); + } + let mounts = Some(mounts.unwrap_or_default()); + let result = python_tool .run( envs, @@ -635,6 +644,7 @@ async def run(c: CONFIG, p: INPUTS) -> OUTPUT: node_name, false, None, + mounts, ) .map_err(|e| LLMProviderError::FunctionExecutionError(e.to_string()))?; let result_str = serde_json::to_string(&result) @@ -678,6 +688,7 @@ async def run(c: CONFIG, p: INPUTS) -> OUTPUT: generate_tool_definitions(tools, CodeLanguage::Typescript, self.sqlite_manager.clone(), false) .await .map_err(|_| ToolError::ExecutionError("Failed to generate tool definitions".to_string()))?; + let envs = generate_execution_environment( context.db(), context.agent().clone().get_id().to_string(), @@ -690,6 +701,14 @@ async def run(c: CONFIG, p: INPUTS) -> OUTPUT: .await .map_err(|e| ToolError::ExecutionError(e.to_string()))?; + let folder = get_app_folder_path(node_env.clone(), context.full_job().job_id().to_string()); + let mounts = Node::v2_api_list_app_files_internal(folder.clone(), true); + if let Err(e) = mounts { + eprintln!("Failed to list app files: {:?}", e); + return Err(LLMProviderError::FunctionExecutionError(format!("{:?}", e))); + } + let mounts = Some(mounts.unwrap_or_default()); + let result = deno_tool .run( envs, @@ -704,6 +723,7 @@ async def run(c: CONFIG, p: INPUTS) -> OUTPUT: node_name, false, Some(tool_id), + mounts, ) .map_err(|e| LLMProviderError::FunctionExecutionError(e.to_string()))?; let result_str = serde_json::to_string(&result) @@ -1052,6 +1072,7 @@ async def run(c: CONFIG, p: INPUTS) -> OUTPUT: requester_node_name, true, Some(tool_id), + None, ) .map_err(|e| LLMProviderError::FunctionExecutionError(e.to_string()))?; let result_str = diff --git a/shinkai-bin/shinkai-node/src/network/handle_commands_list.rs b/shinkai-bin/shinkai-node/src/network/handle_commands_list.rs index 1f64d8296..6b7cc45ed 100644 --- a/shinkai-bin/shinkai-node/src/network/handle_commands_list.rs +++ b/shinkai-bin/shinkai-node/src/network/handle_commands_list.rs @@ -2416,6 +2416,7 @@ impl Node { app_id, llm_provider, extra_config, + mounts, res, } => { let db_clone = Arc::clone(&self.db); @@ -2444,6 +2445,7 @@ impl Node { encryption_secret_key, encryption_public_key, signing_secret_key, + mounts, res, ) .await; @@ -2460,6 +2462,7 @@ impl Node { tool_id, app_id, llm_provider, + mounts, res, } => { let db_clone = Arc::clone(&self.db); @@ -2478,6 +2481,7 @@ impl Node { app_id, llm_provider, node_name, + mounts, res, ) .await; @@ -2603,11 +2607,7 @@ impl Node { let _ = Node::v2_api_import_tool(db_clone, bearer, node_env, url, res).await; }); } - NodeCommand::V2ApiRemoveTool { - bearer, - tool_key, - res, - } => { + NodeCommand::V2ApiRemoveTool { bearer, tool_key, res } => { let db_clone = Arc::clone(&self.db); tokio::spawn(async move { let _ = Node::v2_api_remove_tool(db_clone, bearer, tool_key, res).await; @@ -2680,7 +2680,9 @@ impl Node { let db_clone = Arc::clone(&self.db); let cron_manager_clone = self.cron_manager.clone().unwrap(); tokio::spawn(async move { - let _ = Node::v2_api_force_execute_cron_task(db_clone, cron_manager_clone, bearer, cron_task_id, res).await; + let _ = + Node::v2_api_force_execute_cron_task(db_clone, cron_manager_clone, bearer, cron_task_id, res) + .await; }); } NodeCommand::V2ApiGetCronSchedule { bearer, res } => { @@ -2770,7 +2772,12 @@ impl Node { let _ = Node::v2_export_messages_from_inbox(db_clone, bearer, inbox_name, format, res).await; }); } - NodeCommand::V2ApiSearchShinkaiTool { bearer, query, agent_or_llm, res } => { + NodeCommand::V2ApiSearchShinkaiTool { + bearer, + query, + agent_or_llm, + res, + } => { let db_clone = Arc::clone(&self.db); tokio::spawn(async move { let _ = Node::v2_api_search_shinkai_tool(db_clone, bearer, query, agent_or_llm, res).await; @@ -2873,6 +2880,83 @@ impl Node { .await; }); } + + NodeCommand::V2ApiUploadAppFile { + bearer, + tool_id, + app_id, + file_name, + file_data, + res, + } => { + let db_clone = Arc::clone(&self.db); + let node_env = fetch_node_environment(); + tokio::spawn(async move { + let _ = Node::v2_api_upload_app_file( + db_clone, bearer, tool_id, app_id, file_name, file_data, node_env, res, + ) + .await; + }); + } + NodeCommand::V2ApiGetAppFile { + bearer, + tool_id, + app_id, + file_name, + res, + } => { + let db_clone = Arc::clone(&self.db); + let node_env = fetch_node_environment(); + tokio::spawn(async move { + let _ = + Node::v2_api_get_app_file(db_clone, bearer, tool_id, app_id, file_name, node_env, res).await; + }); + } + NodeCommand::V2ApiUpdateAppFile { + bearer, + tool_id, + app_id, + file_name, + new_name, + file_data, + res, + } => { + let db_clone = Arc::clone(&self.db); + let node_env = fetch_node_environment(); + tokio::spawn(async move { + let _ = Node::v2_api_update_app_file( + db_clone, bearer, tool_id, app_id, file_name, new_name, file_data, node_env, res, + ) + .await; + }); + } + NodeCommand::V2ApiListAppFiles { + bearer, + tool_id, + app_id, + res, + } => { + let db_clone = Arc::clone(&self.db); + let node_env = fetch_node_environment(); + tokio::spawn(async move { + let _ = Node::v2_api_list_app_files(db_clone, bearer, tool_id, app_id, node_env, res).await; + }); + } + NodeCommand::V2ApiDeleteAppFile { + bearer, + tool_id, + app_id, + file_name, + res, + } => { + let db_clone = Arc::clone(&self.db); + let node_env = fetch_node_environment(); + tokio::spawn(async move { + let _ = + Node::v2_api_delete_app_file(db_clone, bearer, tool_id, app_id, file_name, node_env, res).await; + }); + } + _ => (), } } diff --git a/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_app_files.rs b/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_app_files.rs new file mode 100644 index 000000000..6f73eb17e --- /dev/null +++ b/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_app_files.rs @@ -0,0 +1,223 @@ +use crate::network::node_error::NodeError; +use crate::network::Node; +use crate::utils::environment::NodeEnvironment; + +use async_channel::Sender; +use reqwest::StatusCode; +use serde_json::Value; +use shinkai_sqlite::SqliteManager; +use shinkai_tools_primitives::tools::shinkai_tool::ShinkaiTool; + +use std::path::PathBuf; +use std::sync::Arc; + +use shinkai_http_api::node_api_router::APIError; + +pub fn get_app_folder_path(node_env: NodeEnvironment, app_id: String) -> PathBuf { + let mut origin_path: PathBuf = PathBuf::from(node_env.node_storage_path.clone().unwrap_or_default()); + origin_path.push("app_files"); + origin_path.push(ShinkaiTool::convert_to_path(&app_id)); + origin_path +} + +impl Node { + pub async fn v2_api_upload_app_file( + db: Arc, + bearer: String, + tool_id: String, + app_id: String, + file_name: String, + file_data: Vec, + node_env: NodeEnvironment, + res: Sender>, + ) -> Result<(), NodeError> { + // Validate the bearer token + if Self::validate_bearer_token(&bearer, db.clone(), &res).await.is_err() { + return Ok(()); + } + let app_folder_path = get_app_folder_path(node_env, app_id); + if !app_folder_path.exists() { + let result = std::fs::create_dir_all(&app_folder_path); + if let Err(err) = result { + let api_error = APIError { + code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + error: "Internal Server Error".to_string(), + message: format!("Failed to create directory: {}", err), + }; + let _ = res.send(Err(api_error)).await; + return Ok(()); + } + } + let file_path = app_folder_path.join(file_name.clone()); + if let Err(err) = std::fs::write(&file_path, file_data) { + let api_error = APIError { + code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + error: "Internal Server Error".to_string(), + message: format!("Failed to write file: {}", err), + }; + let _ = res.send(Err(api_error)).await; + return Ok(()); + } + let _ = res.send(Ok(Value::String(file_name))).await; + Ok(()) + } + + pub async fn v2_api_get_app_file( + db: Arc, + bearer: String, + tool_id: String, + app_id: String, + file_name: String, + node_env: NodeEnvironment, + res: Sender, APIError>>, + ) -> Result<(), NodeError> { + // Validate the bearer token + if Self::validate_bearer_token(&bearer, db.clone(), &res).await.is_err() { + return Ok(()); + } + let app_folder_path = get_app_folder_path(node_env, app_id); + let file_path = app_folder_path.join(file_name.clone()); + if !file_path.exists() { + let api_error = APIError { + code: StatusCode::NOT_FOUND.as_u16(), + error: "Not Found".to_string(), + message: format!("File {} not found", file_name), + }; + let _ = res.send(Err(api_error)).await; + return Ok(()); + } + let file_bytes = std::fs::read(&file_path)?; + let _ = res.send(Ok(file_bytes)).await; + Ok(()) + } + + pub async fn v2_api_update_app_file( + db: Arc, + bearer: String, + tool_id: String, + app_id: String, + file_name: String, + new_name: Option, + file_data: Option>, + node_env: NodeEnvironment, + res: Sender>, + ) -> Result<(), NodeError> { + // Validate the bearer token + if Self::validate_bearer_token(&bearer, db.clone(), &res).await.is_err() { + return Ok(()); + } + let app_folder_path = get_app_folder_path(node_env, app_id); + let file_path = app_folder_path.join(file_name.clone()); + if !file_path.exists() { + let api_error = APIError { + code: StatusCode::NOT_FOUND.as_u16(), + error: "Not Found".to_string(), + message: format!("File {} not found", file_name), + }; + let _ = res.send(Err(api_error)).await; + return Ok(()); + } + + if let Some(file_data) = file_data { + std::fs::write(&file_path, file_data)?; + } + + if let Some(new_name) = new_name.clone() { + let new_file_path = app_folder_path.join(new_name.clone()); + std::fs::rename(&file_path, &new_file_path)?; + } + + let _ = res.send(Ok(Value::String(new_name.unwrap_or_default()))).await; + Ok(()) + } + + pub async fn v2_api_list_app_files( + db: Arc, + bearer: String, + tool_id: String, + app_id: String, + node_env: NodeEnvironment, + res: Sender>, + ) -> Result<(), NodeError> { + // Validate the bearer token + if Self::validate_bearer_token(&bearer, db.clone(), &res).await.is_err() { + return Ok(()); + } + let app_folder_path = get_app_folder_path(node_env, app_id); + let result = Self::v2_api_list_app_files_internal(app_folder_path, false); + match result { + Ok(file_list) => { + let _ = res + .send(Ok(Value::Array( + file_list.iter().map(|file| Value::String(file.clone())).collect(), + ))) + .await; + Ok(()) + } + Err(err) => { + let _ = res.send(Err(err)).await; + Ok(()) + } + } + } + + pub fn v2_api_list_app_files_internal(app_folder_path: PathBuf, absolute: bool) -> Result, APIError> { + let files = std::fs::read_dir(&app_folder_path); + if let Err(err) = files { + let api_error = APIError { + code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + error: "Internal Server Error".to_string(), + message: format!("Failed to read directory: {}", err), + }; + return Err(api_error); + } + let mut file_list = Vec::new(); + + let files = files.unwrap(); + for file in files { + if let Ok(file) = file { + if absolute { + file_list.push( + file.path() + .canonicalize() + .unwrap_or(file.path()) + .to_string_lossy() + .to_string(), + ); + } else { + file_list.push(file.file_name().to_string_lossy().to_string()); + } + } + } + Ok(file_list) + } + + pub async fn v2_api_delete_app_file( + db: Arc, + bearer: String, + tool_id: String, + app_id: String, + file_name: String, + node_env: NodeEnvironment, + res: Sender>, + ) -> Result<(), NodeError> { + // Validate the bearer token + if Self::validate_bearer_token(&bearer, db.clone(), &res).await.is_err() { + return Ok(()); + } + let app_folder_path = get_app_folder_path(node_env, app_id); + let file_path = app_folder_path.join(file_name.clone()); + if !file_path.exists() { + let api_error = APIError { + code: StatusCode::NOT_FOUND.as_u16(), + error: "Not Found".to_string(), + message: format!("File {} not found", file_name), + }; + let _ = res.send(Err(api_error)).await; + return Ok(()); + } + std::fs::remove_file(&file_path)?; + let _ = res.send(Ok(Value::String(file_name))).await; + Ok(()) + } +} diff --git a/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_tools.rs b/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_tools.rs index bc01d5013..ce3814c07 100644 --- a/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_tools.rs +++ b/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_tools.rs @@ -802,6 +802,7 @@ impl Node { encryption_secret_key: EncryptionStaticKey, encryption_public_key: EncryptionPublicKey, signing_secret_key: SigningKey, + mounts: Option>, res: Sender>, ) -> Result<(), NodeError> { if Self::validate_bearer_token(&bearer, db.clone(), &res).await.is_err() { @@ -828,6 +829,7 @@ impl Node { encryption_secret_key, encryption_public_key, signing_secret_key, + mounts, ) .await; @@ -864,6 +866,7 @@ impl Node { app_id: String, llm_provider: String, node_name: ShinkaiName, + mounts: Option>, res: Sender>, ) -> Result<(), NodeError> { if Self::validate_bearer_token(&bearer, db.clone(), &res).await.is_err() { @@ -890,6 +893,7 @@ impl Node { llm_provider, bearer, node_name, + mounts, ) .await; @@ -1871,7 +1875,6 @@ impl Node { file_name: String, file_data: Vec, node_env: NodeEnvironment, - res: Sender>, ) -> Result<(), NodeError> { // Validate the bearer token diff --git a/shinkai-bin/shinkai-node/src/network/v2_api/mod.rs b/shinkai-bin/shinkai-node/src/network/v2_api/mod.rs index b24225789..a04dd6cff 100644 --- a/shinkai-bin/shinkai-node/src/network/v2_api/mod.rs +++ b/shinkai-bin/shinkai-node/src/network/v2_api/mod.rs @@ -1,4 +1,5 @@ pub mod api_v2_commands; +pub mod api_v2_commands_app_files; pub mod api_v2_commands_cron; pub mod api_v2_commands_ext_agent_offers; pub mod api_v2_commands_jobs; diff --git a/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_coordinator.rs b/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_coordinator.rs index 2e2032648..b95371c18 100644 --- a/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_coordinator.rs +++ b/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_coordinator.rs @@ -128,6 +128,7 @@ pub async fn execute_tool_cmd( encryption_secret_key: EncryptionStaticKey, encryption_public_key: EncryptionPublicKey, signing_secret_key: SigningKey, + mounts: Option>, ) -> Result { eprintln!("[execute_tool] with tool_router_key: {}", tool_router_key); @@ -198,6 +199,7 @@ pub async fn execute_tool_cmd( node_name, true, Some(tool_router_key), + mounts, ) .map(|result| json!(result.data)) } @@ -240,6 +242,7 @@ pub async fn execute_tool_cmd( node_name, true, Some(tool_router_key), + mounts, ) .map(|result| json!(result.data)) .map_err(|e| ToolError::ExecutionError(e.to_string())) @@ -262,6 +265,7 @@ pub async fn execute_code( llm_provider: String, bearer: String, node_name: ShinkaiName, + mounts: Option>, ) -> Result { eprintln!("[execute_code] tool_type: {}", tool_type); // Route based on the prefix @@ -282,6 +286,7 @@ pub async fn execute_code( llm_provider, support_files, code, + mounts, ) .await } @@ -301,6 +306,7 @@ pub async fn execute_code( llm_provider, support_files, code, + mounts, ) .await } diff --git a/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_deno_dynamic.rs b/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_deno_dynamic.rs index 792be68cc..092a56f28 100644 --- a/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_deno_dynamic.rs +++ b/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_deno_dynamic.rs @@ -26,6 +26,7 @@ pub async fn execute_deno_tool( llm_provider: String, support_files: HashMap, code: String, + mounts: Option>, ) -> Result { // Create a minimal DenoTool instance let tool = DenoTool { @@ -101,6 +102,7 @@ pub async fn execute_deno_tool( node_name, false, assets_files, + mounts, ) { Ok(run_result) => Ok(run_result.data), Err(e) => Err(e), diff --git a/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_python_dynamic.rs b/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_python_dynamic.rs index fadc9ae86..81036e92f 100644 --- a/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_python_dynamic.rs +++ b/shinkai-bin/shinkai-node/src/tools/tool_execution/execution_python_dynamic.rs @@ -27,6 +27,7 @@ pub async fn execute_python_tool( llm_provider: String, support_files: HashMap, code: String, + mounts: Option>, ) -> Result { // Create a minimal DenoTool instance let tool = PythonTool { @@ -101,6 +102,7 @@ pub async fn execute_python_tool( node_name, false, assets_files, + mounts, ) { Ok(run_result) => Ok(run_result.data), Err(e) => Err(e), diff --git a/shinkai-bin/shinkai-node/src/wallet/coinbase_mpc_wallet.rs b/shinkai-bin/shinkai-node/src/wallet/coinbase_mpc_wallet.rs index 1598f2b8d..6b8c8b77c 100644 --- a/shinkai-bin/shinkai-node/src/wallet/coinbase_mpc_wallet.rs +++ b/shinkai-bin/shinkai-node/src/wallet/coinbase_mpc_wallet.rs @@ -335,6 +335,7 @@ impl CoinbaseMPCWallet { node_name, false, None, + None, ) .map_err(|e| WalletError::FunctionExecutionError(e.to_string()))?; let result_str = diff --git a/shinkai-bin/shinkai-node/tests/it/utils/node_test_api.rs b/shinkai-bin/shinkai-node/tests/it/utils/node_test_api.rs index ace998448..0e30b0091 100644 --- a/shinkai-bin/shinkai-node/tests/it/utils/node_test_api.rs +++ b/shinkai-bin/shinkai-node/tests/it/utils/node_test_api.rs @@ -683,10 +683,10 @@ pub async fn api_execute_tool( app_id: String, llm_provider: String, extra_config: Map, - oauth: Map, + _oauth: Map, ) -> Result { let (res_sender, res_receiver) = async_channel::bounded(1); - + let mounts = None; node_commands_sender .send(NodeCommand::V2ApiExecuteTool { bearer, @@ -696,6 +696,7 @@ pub async fn api_execute_tool( app_id, llm_provider, extra_config, + mounts, res: res_sender, }) .await diff --git a/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_app_files.rs b/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_app_files.rs new file mode 100644 index 000000000..01ba684a3 --- /dev/null +++ b/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_app_files.rs @@ -0,0 +1,453 @@ +use async_channel::Sender; +use bytes::Buf; +use futures::TryStreamExt; +use reqwest::StatusCode; +use serde::Deserialize; +use std::collections::HashMap; +use utoipa::{OpenApi, ToSchema}; +use warp::multipart::FormData; +use warp::Filter; + +use super::api_v2_router::{create_success_response, with_sender}; +use crate::{node_api_router::APIError, node_commands::NodeCommand}; + +pub fn app_files_routes( + node_commands_sender: Sender, +) -> impl Filter + Clone { + let upload_file_route = warp::path("app_file") + .and(warp::post()) + .and(with_sender(node_commands_sender.clone())) + .and(warp::header::("authorization")) + .and(warp::header::("x-shinkai-tool-id")) + .and(warp::header::("x-shinkai-app-id")) + .and(warp::multipart::form()) + .and_then(upload_file_handler); + + let get_file_route = warp::path("app_file") + .and(warp::get()) + .and(with_sender(node_commands_sender.clone())) + .and(warp::header::("authorization")) + .and(warp::header::("x-shinkai-tool-id")) + .and(warp::header::("x-shinkai-app-id")) + .and(warp::query::>()) + .and_then(get_file_handler); + + let update_file_route = warp::path("patch_app_file") + .and(warp::post()) + .and(with_sender(node_commands_sender.clone())) + .and(warp::header::("authorization")) + .and(warp::header::("x-shinkai-tool-id")) + .and(warp::header::("x-shinkai-app-id")) + .and(warp::multipart::form()) + .and_then(update_file_handler); + + let list_files_route = warp::path("list_app_files") + .and(warp::get()) + .and(with_sender(node_commands_sender.clone())) + .and(warp::header::("authorization")) + .and(warp::header::("x-shinkai-tool-id")) + .and(warp::header::("x-shinkai-app-id")) + .and_then(list_files_handler); + + let delete_file_route = warp::path("app_file") + .and(warp::delete()) + .and(with_sender(node_commands_sender.clone())) + .and(warp::header::("authorization")) + .and(warp::header::("x-shinkai-tool-id")) + .and(warp::header::("x-shinkai-app-id")) + .and(warp::query::>()) + .and_then(delete_file_handler); + + upload_file_route + .or(get_file_route) + .or(update_file_route) + .or(list_files_route) + .or(delete_file_route) +} + +pub fn safe_folder_name(tool_router_key: &str, is_file_name: bool) -> String { + tool_router_key + .chars() + .map(|c| { + if c.is_ascii_alphanumeric() || c == '-' || c == '_' || (is_file_name && c == '.') { + c + } else { + '_' + } + }) + .collect::() + .to_lowercase() +} + +#[utoipa::path( + post, + path = "/v2/app_file", + responses( + (status = 200, description = "Successfully uploaded file", body = Value), + (status = 400, description = "Bad request", body = APIError), + (status = 500, description = "Internal server error", body = APIError) + ) +)] +pub async fn upload_file_handler( + sender: Sender, + authorization: String, + tool_id: String, + app_id: String, + mut form: FormData, +) -> Result { + let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string(); + + let mut file_name = String::new(); + let mut file_data: Option> = Some(vec![]); + + while let Ok(Some(part)) = form.try_next().await { + match part.name() { + "file_name" => { + // Convert the part to bytes then to string + let mut bytes = Vec::new(); + let mut stream = part.stream(); + while let Ok(Some(chunk)) = stream.try_next().await { + bytes.extend_from_slice(chunk.chunk()); + } + file_name = String::from_utf8_lossy(&bytes).into_owned(); + file_name = safe_folder_name(&file_name, true); + } + "file" => { + // Read file data + let mut bytes = Vec::new(); + let mut stream = part.stream(); + while let Ok(Some(chunk)) = stream.try_next().await { + bytes.extend_from_slice(chunk.chunk()); + } + file_data = Some(bytes); + } + _ => {} + } + } + + if file_name.is_empty() { + return Ok(warp::reply::with_status( + warp::reply::json(&APIError { + code: 400, + error: "Missing file name".to_string(), + message: "File name is required".to_string(), + }), + StatusCode::BAD_REQUEST, + )); + } + + let (res_sender, res_receiver) = async_channel::bounded(1); + + sender + .send(NodeCommand::V2ApiUploadAppFile { + bearer, + tool_id: safe_folder_name(&tool_id, false), + app_id: safe_folder_name(&app_id, false), + file_name, + file_data: file_data.unwrap(), + res: res_sender, + }) + .await + .map_err(|_| warp::reject::reject())?; + + let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?; + + match result { + Ok(response) => { + let response = create_success_response(response); + Ok(warp::reply::with_status(warp::reply::json(&response), StatusCode::OK)) + } + Err(error) => Ok(warp::reply::with_status( + warp::reply::json(&error), + StatusCode::from_u16(error.code).unwrap(), + )), + } +} + +#[derive(Deserialize, ToSchema)] +pub struct UpdateFileRequest { + pub file_name: String, + pub new_name: Option, + pub file_data: Option>, +} + +#[utoipa::path( + post, + path = "/v2/patch_app_file", + request_body(content_type = "multipart/form-data", content = UpdateFileRequest, description = "File to update"), + responses( + (status = 200, description = "Successfully updated file", body = Value), + (status = 400, description = "Bad request", body = APIError), + (status = 500, description = "Internal server error", body = APIError) + ) +)] +pub async fn update_file_handler( + sender: Sender, + authorization: String, + tool_id: String, + app_id: String, + mut form: FormData, +) -> Result { + let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string(); + + let (res_sender, res_receiver) = async_channel::bounded(1); + + let mut file_name: String = String::new(); + let mut new_name: Option = None; + let mut file_data: Option> = None; + + while let Ok(Some(part)) = form.try_next().await { + match part.name() { + "file_name" => { + // Convert the part to bytes then to string + let mut bytes = Vec::new(); + let mut stream = part.stream(); + while let Ok(Some(chunk)) = stream.try_next().await { + bytes.extend_from_slice(chunk.chunk()); + } + file_name = String::from_utf8_lossy(&bytes).into_owned(); + } + "file" => { + // Read file data + let mut bytes = Vec::new(); + let mut stream = part.stream(); + while let Ok(Some(chunk)) = stream.try_next().await { + bytes.extend_from_slice(chunk.chunk()); + } + file_data = Some(bytes); + } + "new_name" => { + // Convert the part to bytes then to string + let mut bytes = Vec::new(); + let mut stream = part.stream(); + while let Ok(Some(chunk)) = stream.try_next().await { + bytes.extend_from_slice(chunk.chunk()); + } + new_name = Some(safe_folder_name(&String::from_utf8_lossy(&bytes).into_owned(), true)); + } + _ => {} + } + } + if file_name.is_empty() { + return Ok(warp::reply::with_status( + warp::reply::json(&APIError { + code: 400, + error: "Missing file name".to_string(), + message: "File name is required".to_string(), + }), + StatusCode::BAD_REQUEST, + )); + } + + sender + .send(NodeCommand::V2ApiUpdateAppFile { + bearer, + tool_id: safe_folder_name(&tool_id, false), + app_id: safe_folder_name(&app_id, false), + file_name, + new_name, + file_data, + res: res_sender, + }) + .await + .map_err(|_| warp::reject::reject())?; + + let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?; + + match result { + Ok(response) => { + let response = create_success_response(response); + Ok(warp::reply::with_status(warp::reply::json(&response), StatusCode::OK)) + } + Err(error) => Ok(warp::reply::with_status( + warp::reply::json(&error), + StatusCode::from_u16(error.code).unwrap(), + )), + } +} + +#[utoipa::path( + get, + path = "/v2/app_file", + params( + ("file_name" = String, Query, description = "Name of the file to retrieve") + ), + responses( + (status = 200, description = "Successfully retrieved file", body = Vec), + (status = 400, description = "Bad request", body = APIError), + (status = 500, description = "Internal server error", body = APIError) + ) +)] +pub async fn get_file_handler( + sender: Sender, + authorization: String, + tool_id: String, + app_id: String, + query_params: HashMap, +) -> Result { + let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string(); + + let file_name = query_params + .get("file_name") + .ok_or_else(|| { + warp::reject::custom(APIError { + code: 400, + error: "Missing file name".to_string(), + message: "File name is required".to_string(), + }) + })? + .to_string(); + + let (res_sender, res_receiver) = async_channel::bounded(1); + + sender + .send(NodeCommand::V2ApiGetAppFile { + bearer, + tool_id: safe_folder_name(&tool_id, false), + app_id: safe_folder_name(&app_id, false), + file_name, + res: res_sender, + }) + .await + .map_err(|_| warp::reject::reject())?; + + let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?; + + match result { + Ok(file_bytes) => Ok(warp::reply::with_header( + warp::reply::with_status(file_bytes, StatusCode::OK), + "Content-Type", + "application/octet-stream", + )), + Err(error) => Ok(warp::reply::with_header( + warp::reply::with_status( + error.message.as_bytes().to_vec(), + StatusCode::from_u16(error.code).unwrap(), + ), + "Content-Type", + "text/plain", + )), + } +} + +#[utoipa::path( + get, + path = "/v2/list_app_files", + responses( + (status = 200, description = "Successfully listed files", body = Vec), + (status = 400, description = "Bad request", body = APIError), + (status = 500, description = "Internal server error", body = APIError) + ) +)] +pub async fn list_files_handler( + sender: Sender, + authorization: String, + tool_id: String, + app_id: String, +) -> Result { + let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string(); + + let (res_sender, res_receiver) = async_channel::bounded(1); + + sender + .send(NodeCommand::V2ApiListAppFiles { + bearer, + tool_id: safe_folder_name(&tool_id, false), + app_id: safe_folder_name(&app_id, false), + res: res_sender, + }) + .await + .map_err(|_| warp::reject::reject())?; + + let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?; + + match result { + Ok(response) => { + let response = create_success_response(response); + Ok(warp::reply::with_status(warp::reply::json(&response), StatusCode::OK)) + } + Err(error) => Ok(warp::reply::with_status( + warp::reply::json(&error), + StatusCode::from_u16(error.code).unwrap(), + )), + } +} + +#[utoipa::path( + delete, + path = "/v2/app_file", + params( + ("file_name" = String, Query, description = "Name of the file to delete") + ), + responses( + (status = 200, description = "Successfully deleted file", body = Value), + (status = 400, description = "Bad request", body = APIError), + (status = 500, description = "Internal server error", body = APIError) + ) +)] +pub async fn delete_file_handler( + sender: Sender, + authorization: String, + tool_id: String, + app_id: String, + query_params: HashMap, +) -> Result { + let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string(); + + let file_name = query_params + .get("file_name") + .ok_or_else(|| { + warp::reject::custom(APIError { + code: 400, + error: "Missing file name".to_string(), + message: "File name is required".to_string(), + }) + })? + .to_string(); + + let (res_sender, res_receiver) = async_channel::bounded(1); + + sender + .send(NodeCommand::V2ApiDeleteAppFile { + bearer, + tool_id: safe_folder_name(&tool_id, false), + app_id: safe_folder_name(&app_id, false), + file_name, + res: res_sender, + }) + .await + .map_err(|_| warp::reject::reject())?; + + let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?; + + match result { + Ok(response) => { + let response = create_success_response(response); + Ok(warp::reply::with_status(warp::reply::json(&response), StatusCode::OK)) + } + Err(error) => Ok(warp::reply::with_status( + warp::reply::json(&error), + StatusCode::from_u16(error.code).unwrap(), + )), + } +} + +#[derive(OpenApi)] +#[openapi( + paths( + upload_file_handler, + get_file_handler, + update_file_handler, + list_files_handler, + delete_file_handler, + ), + components( + schemas( + APIError, + ) + ), + tags( + (name = "files", description = "File API endpoints") + ) +)] +pub struct FilesApiDoc; diff --git a/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_tools.rs b/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_tools.rs index f2fef6489..397701f70 100644 --- a/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_tools.rs +++ b/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_tools.rs @@ -7,9 +7,8 @@ use utoipa::{OpenApi, ToSchema}; use warp::Filter; use reqwest::StatusCode; use std::collections::HashMap; -use bytes::Bytes; use futures::TryStreamExt; -use warp::multipart::{FormData, Part}; +use warp::multipart::{FormData}; use bytes::Buf; use crate::{node_api_router::APIError, node_commands::NodeCommand}; @@ -53,7 +52,6 @@ pub fn tool_routes( .and(warp::body::json()) .and_then(add_shinkai_tool_handler); - let tool_execution_route = warp::path("tool_execution") .and(warp::post()) .and(with_sender(node_commands_sender.clone())) @@ -222,6 +220,20 @@ pub fn tool_routes( .or(remove_tool_route) } +pub fn safe_folder_name(tool_router_key: &str) -> String { + tool_router_key + .chars() + .map(|c| { + if c.is_ascii_alphanumeric() || c == '-' || c == '_' { + c + } else { + '_' + } + }) + .collect::() + .to_lowercase() +} + #[utoipa::path( get, path = "/v2/tool_definitions", @@ -295,7 +307,8 @@ pub struct ToolExecutionRequest { pub llm_provider: String, pub parameters: Value, #[serde(default = "default_map")] - pub extra_config: Value + pub extra_config: Value, + pub mounts: Option>, } #[utoipa::path( @@ -342,10 +355,11 @@ pub async fn tool_execution_handler( bearer, tool_router_key: payload.tool_router_key.clone(), parameters, - tool_id, - app_id, + tool_id: safe_folder_name(&tool_id), + app_id: safe_folder_name(&app_id), llm_provider: payload.llm_provider.clone(), extra_config, + mounts: payload.mounts, res: res_sender, }) .await @@ -739,8 +753,8 @@ pub async fn set_playground_tool_handler( .send(NodeCommand::V2ApiSetPlaygroundTool { bearer, payload, - tool_id, - app_id, + tool_id: safe_folder_name(&tool_id), + app_id: safe_folder_name(&app_id), res: res_sender, }) .await @@ -971,6 +985,7 @@ pub struct CodeExecutionRequest { pub oauth: Option>, pub llm_provider: String, pub tools: Vec, + pub mounts: Option>, } // Define a custom default function for oauth @@ -1028,9 +1043,10 @@ pub async fn code_execution_handler( parameters, extra_config, oauth: payload.oauth, - tool_id: tool_id, - app_id: app_id, + tool_id: safe_folder_name(&tool_id), + app_id: safe_folder_name(&app_id), llm_provider: payload.llm_provider, + mounts: payload.mounts, res: res_sender, }) .await @@ -1443,8 +1459,8 @@ pub async fn tool_asset_handler( sender .send(NodeCommand::V2ApiUploadToolAsset { bearer, - tool_id, - app_id, + tool_id: safe_folder_name(&tool_id), + app_id: safe_folder_name(&app_id), file_name, file_data, res: res_sender, @@ -1488,8 +1504,8 @@ pub async fn list_tool_asset_handler( sender .send(NodeCommand::V2ApiListToolAssets { bearer, - tool_id, - app_id, + tool_id: safe_folder_name(&tool_id), + app_id: safe_folder_name(&app_id), res: res_sender, }) .await @@ -1546,8 +1562,8 @@ pub async fn delete_tool_asset_handler( sender .send(NodeCommand::V2ApiDeleteToolAsset { bearer, - tool_id, - app_id, + tool_id: safe_folder_name(&tool_id), + app_id: safe_folder_name(&app_id), file_name, res: res_sender, }) @@ -1605,4 +1621,5 @@ pub async fn delete_tool_asset_handler( (name = "tools", description = "Tool API endpoints") ) )] + pub struct ToolsApiDoc; diff --git a/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_router.rs b/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_router.rs index e157d7e2a..e1004eb4a 100644 --- a/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_router.rs +++ b/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_router.rs @@ -1,5 +1,6 @@ use crate::node_commands::NodeCommand; +use super::api_v2_handlers_app_files::app_files_routes; use super::api_v2_handlers_cron::cron_routes; use super::api_v2_handlers_ext_agent_offers::ext_agent_offers_routes; use super::api_v2_handlers_general::general_routes; @@ -32,7 +33,7 @@ pub fn v2_routes( let tool_routes = tool_routes(node_commands_sender.clone()); let cron_routes = cron_routes(node_commands_sender.clone(), node_name.clone()); let oauth_routes = oauth_routes(node_commands_sender.clone()); - + let app_files_routes = app_files_routes(node_commands_sender.clone()); general_routes .or(vecfs_routes) .or(job_routes) @@ -44,6 +45,7 @@ pub fn v2_routes( .or(tool_routes) .or(cron_routes) .or(oauth_routes) + .or(app_files_routes) } pub fn with_sender( diff --git a/shinkai-libs/shinkai-http-api/src/api_v2/mod.rs b/shinkai-libs/shinkai-http-api/src/api_v2/mod.rs index f9e6e8aa4..895717c2e 100644 --- a/shinkai-libs/shinkai-http-api/src/api_v2/mod.rs +++ b/shinkai-libs/shinkai-http-api/src/api_v2/mod.rs @@ -1,3 +1,4 @@ +pub mod api_v2_handlers_app_files; pub mod api_v2_handlers_cron; pub mod api_v2_handlers_ext_agent_offers; pub mod api_v2_handlers_general; diff --git a/shinkai-libs/shinkai-http-api/src/node_commands.rs b/shinkai-libs/shinkai-http-api/src/node_commands.rs index a6d8bdb1a..5b598eafc 100644 --- a/shinkai-libs/shinkai-http-api/src/node_commands.rs +++ b/shinkai-libs/shinkai-http-api/src/node_commands.rs @@ -943,6 +943,7 @@ pub enum NodeCommand { app_id: String, llm_provider: String, extra_config: Map, + mounts: Option>, res: Sender>, }, V2ApiExecuteCode { @@ -956,6 +957,7 @@ pub enum NodeCommand { tool_id: String, app_id: String, llm_provider: String, + mounts: Option>, res: Sender>, }, V2ApiGenerateToolDefinitions { @@ -1130,4 +1132,41 @@ pub enum NodeCommand { file_name: String, res: Sender>, }, + V2ApiUploadAppFile { + bearer: String, + tool_id: String, + app_id: String, + file_name: String, + file_data: Vec, + res: Sender>, + }, + V2ApiGetAppFile { + bearer: String, + tool_id: String, + app_id: String, + file_name: String, + res: Sender, APIError>>, + }, + V2ApiUpdateAppFile { + bearer: String, + tool_id: String, + app_id: String, + file_name: String, + new_name: Option, + file_data: Option>, + res: Sender>, + }, + V2ApiListAppFiles { + bearer: String, + tool_id: String, + app_id: String, + res: Sender>, + }, + V2ApiDeleteAppFile { + bearer: String, + tool_id: String, + app_id: String, + file_name: String, + res: Sender>, + }, } diff --git a/shinkai-libs/shinkai-tools-primitives/src/tools/deno_tools.rs b/shinkai-libs/shinkai-tools-primitives/src/tools/deno_tools.rs index 616afcc1f..01d9bfe95 100644 --- a/shinkai-libs/shinkai-tools-primitives/src/tools/deno_tools.rs +++ b/shinkai-libs/shinkai-tools-primitives/src/tools/deno_tools.rs @@ -76,6 +76,7 @@ impl DenoTool { node_name: ShinkaiName, is_temporary: bool, files_tool_router_key: Option, + mounts: Option>, ) -> Result { let assets_files = match files_tool_router_key { Some(tool_router_key) => { @@ -106,6 +107,7 @@ impl DenoTool { node_name, is_temporary, assets_files, + mounts, ) } @@ -123,6 +125,7 @@ impl DenoTool { node_name: ShinkaiName, is_temporary: bool, assets_files: Vec, + mounts: Option>, ) -> Result { println!( "[Running DenoTool] Named: {}, Input: {:?}, Extra Config: {:?}", @@ -218,6 +221,13 @@ impl DenoTool { code_files.insert(format!("{}.ts", file_name), file_code.clone()); }); + let mount_files = mounts + .clone() + .unwrap_or_default() + .iter() + .map(|mount| PathBuf::from(mount)) + .collect(); + // Setup the engine with the code files and config let tool = DenoRunner::new( CodeFiles { @@ -232,7 +242,7 @@ impl DenoTool { code_id: "".to_string(), storage: full_path.clone(), assets_files, - mount_files: vec![], + mount_files, }, deno_binary_path: PathBuf::from( env::var("SHINKAI_TOOLS_RUNNER_DENO_BINARY_PATH") diff --git a/shinkai-libs/shinkai-tools-primitives/src/tools/python_tools.rs b/shinkai-libs/shinkai-tools-primitives/src/tools/python_tools.rs index 404422a81..516d433b9 100644 --- a/shinkai-libs/shinkai-tools-primitives/src/tools/python_tools.rs +++ b/shinkai-libs/shinkai-tools-primitives/src/tools/python_tools.rs @@ -75,6 +75,7 @@ impl PythonTool { node_name: ShinkaiName, is_temporary: bool, files_tool_router_key: Option, + mounts: Option>, ) -> Result { let assets_files = match files_tool_router_key { Some(tool_router_key) => { @@ -105,6 +106,7 @@ impl PythonTool { node_name, is_temporary, assets_files, + mounts, ) } @@ -122,6 +124,7 @@ impl PythonTool { node_name: ShinkaiName, is_temporary: bool, assets_files: Vec, + mounts: Option>, ) -> Result { println!( "[Running DenoTool] Named: {}, Input: {:?}, Extra Config: {:?}", @@ -237,7 +240,12 @@ impl PythonTool { code_id: "".to_string(), storage: full_path.clone(), assets_files, - mount_files: vec![], + mount_files: mounts + .clone() + .unwrap_or_default() + .iter() + .map(|mount| PathBuf::from(mount)) + .collect(), }, uv_binary_path: PathBuf::from( env::var("SHINKAI_TOOLS_RUNNER_UV_BINARY_PATH")