Skip to content
Merged
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
10 changes: 9 additions & 1 deletion crates/goose-cli/src/recipes/extract_from_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,18 +240,26 @@ settings:
temperature: 0.7
sub_recipes:
- path: existing_sub_recipe.yaml
name: existing_sub_recipe
name: existing_sub_recipe
response:
json_schema:
type: object
properties:
result:
type: string
"#;
let sub_recipe_content = r#"
title: existing_sub_recipe
description: An existing sub recipe
instructions: sub recipe instructions
prompt: sub recipe prompt
"#;
let temp_dir = tempfile::tempdir().unwrap();
let recipe_path: std::path::PathBuf = temp_dir.path().join("test_recipe.yaml");
let sub_recipe_path: std::path::PathBuf = temp_dir.path().join("existing_sub_recipe.yaml");

std::fs::write(&recipe_path, test_recipe_content).unwrap();
std::fs::write(&sub_recipe_path, sub_recipe_content).unwrap();
let canonical_recipe_path = recipe_path.canonicalize().unwrap();
(temp_dir, canonical_recipe_path)
}
Expand Down
21 changes: 9 additions & 12 deletions crates/goose/src/recipe/build_recipe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ where

if let Some(ref mut sub_recipes) = recipe.sub_recipes {
for sub_recipe in sub_recipes {
if let Ok(resolved_path) = resolve_sub_recipe_path(&sub_recipe.path, recipe_dir) {
sub_recipe.path = resolved_path;
}
sub_recipe.path = resolve_sub_recipe_path(&sub_recipe.path, recipe_dir)?;
}
}

Expand Down Expand Up @@ -122,18 +120,17 @@ fn resolve_sub_recipe_path(
parent_recipe_dir: &Path,
) -> Result<String, RecipeError> {
let path = if Path::new(sub_recipe_path).is_absolute() {
sub_recipe_path.to_string()
Path::new(sub_recipe_path).to_path_buf()
} else {
parent_recipe_dir
.join(sub_recipe_path)
.to_str()
.ok_or_else(|| RecipeError::RecipeParsing {
source: anyhow::anyhow!("Invalid sub-recipe path: {}", sub_recipe_path),
})?
.to_string()
parent_recipe_dir.join(sub_recipe_path)
};
if !path.exists() {
return Err(RecipeError::RecipeParsing {
source: anyhow::anyhow!("Sub-recipe file does not exist: {}", path.display()),
});
}

Ok(path)
Ok(path.display().to_string())
}

#[cfg(test)]
Expand Down
40 changes: 37 additions & 3 deletions crates/goose/src/recipe/build_recipe/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,14 @@ mod sub_recipe_path_resolution {
let temp_dir = tempfile::tempdir().unwrap();
let parent_dir = temp_dir.path();

// Create the sub-recipe file
let sub_recipe_content = r#"
version: 1.0.0
title: Child Recipe
description: A child recipe
instructions: Child instructions"#;
create_recipe_file(parent_dir, "sub-recipes", "child.yaml", sub_recipe_content);

let result = resolve_sub_recipe_path("./sub-recipes/child.yaml", parent_dir);
assert!(result.is_ok());

Expand All @@ -446,11 +454,37 @@ mod sub_recipe_path_resolution {
fn test_resolve_sub_recipe_path_absolute() {
let temp_dir = tempfile::tempdir().unwrap();
let parent_dir = temp_dir.path();
let absolute_path = "/absolute/path/to/recipe.yaml";

let result = resolve_sub_recipe_path(absolute_path, parent_dir);
let sub_recipe_content = r#"
version: 1.0.0
title: Absolute Recipe
description: A recipe with absolute path
instructions: Absolute instructions"#;
let absolute_path =
create_recipe_file(parent_dir, "absolute", "recipe.yaml", sub_recipe_content);
let absolute_path_str = absolute_path.to_str().unwrap();

let result = resolve_sub_recipe_path(absolute_path_str, parent_dir);
assert!(result.is_ok());
assert_eq!(result.unwrap(), absolute_path);
assert_eq!(result.unwrap(), absolute_path_str);
}

#[test]
fn test_resolve_sub_recipe_path_nonexistent() {
let temp_dir = tempfile::tempdir().unwrap();
let parent_dir = temp_dir.path();

let result = resolve_sub_recipe_path("./sub-recipes/nonexistent.yaml", parent_dir);

assert!(result.is_err());
match result {
Err(RecipeError::RecipeParsing { source }) => {
let error_msg = source.to_string();
assert!(error_msg.contains("Sub-recipe file does not exist"));
assert!(error_msg.contains("nonexistent.yaml"));
}
_ => panic!("Expected RecipeError::RecipeParsing"),
}
}

#[test]
Expand Down
14 changes: 12 additions & 2 deletions ui/desktop/src/hooks/useRecipeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { substituteParameters } from '../utils/providerUtils';
import { updateSessionUserRecipeValues } from '../api';
import { useChatContext } from '../contexts/ChatContext';
import { ChatType } from '../types/chat';
import { toastSuccess } from '../toasts';
import { toastError, toastSuccess } from '../toasts';

export const useRecipeManager = (chat: ChatType, recipe?: Recipe | null) => {
const [isParameterModalOpen, setIsParameterModalOpen] = useState(false);
Expand Down Expand Up @@ -181,7 +181,17 @@ export const useRecipeManager = (chat: ChatType, recipe?: Recipe | null) => {
}
setIsParameterModalOpen(false);
} catch (error) {
console.error('Failed to update system prompt with parameters:', error);
let error_message = 'unknown error';
if (typeof error === 'object' && error !== null && 'message' in error) {
error_message = error.message as string;
} else if (typeof error === 'string') {
error_message = error;
}
console.error('Failed to render recipe with parameters:', error);
toastError({
title: 'Recipe Rendering Failed',
msg: error_message,
});
}
};

Expand Down
Loading