Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/goose-cli/src/recipes/extract_from_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub fn extract_recipe_info_from_cli(
path: recipe_file_path.to_string_lossy().to_string(),
name,
values: None,
config: None,
};
all_sub_recipes.push(additional_sub_recipe);
}
Expand Down Expand Up @@ -111,6 +112,7 @@ mod tests {
assert_eq!(sub_recipes[0].path, "existing_sub_recipe.yaml".to_string());
assert_eq!(sub_recipes[0].name, "existing_sub_recipe".to_string());
assert!(sub_recipes[0].values.is_none());
assert!(sub_recipes[0].config.is_none());
assert!(response.is_some());
let response = response.unwrap();
assert_eq!(
Expand Down Expand Up @@ -167,6 +169,7 @@ mod tests {
assert_eq!(sub_recipes[0].path, "existing_sub_recipe.yaml".to_string());
assert_eq!(sub_recipes[0].name, "existing_sub_recipe".to_string());
assert!(sub_recipes[0].values.is_none());
assert!(sub_recipes[0].config.is_none());
assert_eq!(
sub_recipes[1].path,
sub_recipe1_path
Expand All @@ -177,6 +180,7 @@ mod tests {
);
assert_eq!(sub_recipes[1].name, "sub_recipe1".to_string());
assert!(sub_recipes[1].values.is_none());
assert!(sub_recipes[1].config.is_none());
assert_eq!(
sub_recipes[2].path,
sub_recipe2_path
Expand All @@ -187,6 +191,7 @@ mod tests {
);
assert_eq!(sub_recipes[2].name, "sub_recipe2".to_string());
assert!(sub_recipes[2].values.is_none());
assert!(sub_recipes[2].config.is_none());
assert!(response.is_some());
let response = response.unwrap();
assert_eq!(
Expand Down
9 changes: 8 additions & 1 deletion crates/goose/src/agents/recipe_tools/sub_recipe_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,20 @@ fn prepare_command_params(

pub async fn create_sub_recipe_task(sub_recipe: &SubRecipe, params: Value) -> Result<String> {
let command_params = prepare_command_params(sub_recipe, params)?;
let payload = json!({
let mut payload = json!({
"sub_recipe": {
"name": sub_recipe.name.clone(),
"command_parameters": command_params,
"recipe_path": sub_recipe.path.clone(),
}
});

// Include config if present
if let Some(config) = &sub_recipe.config {
payload["config"] = serde_json::to_value(config)
.map_err(|e| anyhow::anyhow!("Failed to serialize config: {}", e))?;
}

let task = Task {
id: uuid::Uuid::new_v4().to_string(),
task_type: "sub_recipe".to_string(),
Expand Down
87 changes: 87 additions & 0 deletions crates/goose/src/agents/recipe_tools/sub_recipe_tools/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod tests {
name: "test_sub_recipe".to_string(),
path: "test_sub_recipe.yaml".to_string(),
values: Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
config: None,
};
sub_recipe
}
Expand Down Expand Up @@ -42,6 +43,7 @@ mod tests {
name: "test_sub_recipe".to_string(),
path: "test_sub_recipe.yaml".to_string(),
values: None,
config: None,
};
let params: HashMap<String, String> = HashMap::new();
let params_value = serde_json::to_value(params).unwrap();
Expand Down Expand Up @@ -113,6 +115,7 @@ mod tests {
name: "test_sub_recipe".to_string(),
path: "test_sub_recipe.yaml".to_string(),
values: None,
config: None,
};

let sub_recipe_file_content = r#"{
Expand Down Expand Up @@ -152,4 +155,88 @@ mod tests {
assert_eq!(result["required"][0], "key1");
}
}

mod create_sub_recipe_task_tests {
use serde_json::Value;

use crate::{
agents::recipe_tools::sub_recipe_tools::{
create_sub_recipe_task, tests::tests::setup_sub_recipe,
},
recipe::SubRecipeConfig,
};

#[tokio::test]
async fn test_create_sub_recipe_task_with_config() {
let mut sub_recipe = setup_sub_recipe();
sub_recipe.config = Some(SubRecipeConfig {
timeout_seconds: Some(600),
max_workers: Some(5),
initial_workers: Some(3),
});

let params = serde_json::json!({
"param1": "value1"
});

let result = create_sub_recipe_task(&sub_recipe, params).await.unwrap();
let task: Value = serde_json::from_str(&result).unwrap();

// Verify the task structure
assert_eq!(task["task_type"], "sub_recipe");
assert!(task["payload"]["sub_recipe"].is_object());
assert!(task["payload"]["config"].is_object());

let config = &task["payload"]["config"];
assert_eq!(config["timeout_seconds"], 600);
assert_eq!(config["max_workers"], 5);
assert_eq!(config["initial_workers"], 3);
}

#[tokio::test]
async fn test_create_sub_recipe_task_without_config() {
let sub_recipe = setup_sub_recipe();

let params = serde_json::json!({
"param1": "value1"
});

let result = create_sub_recipe_task(&sub_recipe, params).await.unwrap();
let task: Value = serde_json::from_str(&result).unwrap();

// Verify the task structure
assert_eq!(task["task_type"], "sub_recipe");
assert!(task["payload"]["sub_recipe"].is_object());
assert!(task["payload"]["config"].is_null());
}

#[tokio::test]
async fn test_create_sub_recipe_task_with_partial_config() {
let mut sub_recipe = setup_sub_recipe();
sub_recipe.config = Some(SubRecipeConfig {
timeout_seconds: Some(600),
max_workers: None, // This should not appear in JSON
initial_workers: None, // This should not appear in JSON
});

let params = serde_json::json!({
"param1": "value1"
});

let result = create_sub_recipe_task(&sub_recipe, params).await.unwrap();
let task: Value = serde_json::from_str(&result).unwrap();

// Verify the task structure
assert_eq!(task["task_type"], "sub_recipe");
assert!(task["payload"]["sub_recipe"].is_object());
assert!(task["payload"]["config"].is_object());

let config = &task["payload"]["config"];
assert_eq!(config["timeout_seconds"], 600);

// These fields should not be present (not even as null) due to skip_serializing_if
assert!(!config.as_object().unwrap().contains_key("max_workers"));
assert!(!config.as_object().unwrap().contains_key("initial_workers"));
}
}
}
Loading
Loading