mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-17 22:24:21 +01:00
247 lines
8.7 KiB
Rust
247 lines
8.7 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use goose::recipe::SubRecipe;
|
|
|
|
use crate::recipes::print_recipe::print_recipe_info;
|
|
use crate::recipes::recipe::load_recipe;
|
|
use crate::recipes::search_recipe::retrieve_recipe_file;
|
|
use crate::{
|
|
cli::{InputConfig, RecipeInfo},
|
|
session::SessionSettings,
|
|
};
|
|
|
|
pub fn extract_recipe_info_from_cli(
|
|
recipe_name: String,
|
|
params: Vec<(String, String)>,
|
|
additional_sub_recipes: Vec<String>,
|
|
) -> Result<(InputConfig, RecipeInfo)> {
|
|
let recipe = load_recipe(&recipe_name, params.clone()).unwrap_or_else(|err| {
|
|
eprintln!("{}: {}", console::style("Error").red().bold(), err);
|
|
std::process::exit(1);
|
|
});
|
|
print_recipe_info(&recipe, params);
|
|
let mut all_sub_recipes = recipe.sub_recipes.clone().unwrap_or_default();
|
|
if !additional_sub_recipes.is_empty() {
|
|
for sub_recipe_name in additional_sub_recipes {
|
|
match retrieve_recipe_file(&sub_recipe_name) {
|
|
Ok(recipe_file) => {
|
|
let name = extract_recipe_name(&sub_recipe_name);
|
|
let recipe_file_path = recipe_file.file_path;
|
|
let additional_sub_recipe = SubRecipe {
|
|
path: recipe_file_path.to_string_lossy().to_string(),
|
|
name,
|
|
values: None,
|
|
sequential_when_repeated: true,
|
|
};
|
|
all_sub_recipes.push(additional_sub_recipe);
|
|
}
|
|
Err(e) => {
|
|
return Err(anyhow!(
|
|
"Could not retrieve sub-recipe '{}': {}",
|
|
sub_recipe_name,
|
|
e
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let input_config = InputConfig {
|
|
contents: recipe.prompt.filter(|s| !s.trim().is_empty()),
|
|
extensions_override: recipe.extensions,
|
|
additional_system_prompt: recipe.instructions,
|
|
};
|
|
|
|
let recipe_info = RecipeInfo {
|
|
session_settings: recipe.settings.map(|s| SessionSettings {
|
|
goose_provider: s.goose_provider,
|
|
goose_model: s.goose_model,
|
|
temperature: s.temperature,
|
|
}),
|
|
sub_recipes: Some(all_sub_recipes),
|
|
final_output_response: recipe.response,
|
|
retry_config: recipe.retry,
|
|
};
|
|
|
|
Ok((input_config, recipe_info))
|
|
}
|
|
|
|
fn extract_recipe_name(recipe_identifier: &str) -> String {
|
|
// If it's a path (contains / or \), extract the file stem
|
|
if recipe_identifier.contains('/') || recipe_identifier.contains('\\') {
|
|
PathBuf::from(recipe_identifier)
|
|
.file_stem()
|
|
.and_then(|s| s.to_str())
|
|
.unwrap_or("unknown")
|
|
.to_string()
|
|
} else {
|
|
// If it's just a name (like "weekly-updates"), use it directly
|
|
recipe_identifier.to_string()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::path::PathBuf;
|
|
|
|
use tempfile::TempDir;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_extract_recipe_info_from_cli_basic() {
|
|
let (_temp_dir, recipe_path) = create_recipe();
|
|
let params = vec![("name".to_string(), "my_value".to_string())];
|
|
let recipe_name = recipe_path.to_str().unwrap().to_string();
|
|
|
|
let (input_config, recipe_info) =
|
|
extract_recipe_info_from_cli(recipe_name, params, Vec::new()).unwrap();
|
|
let settings = recipe_info.session_settings;
|
|
let sub_recipes = recipe_info.sub_recipes;
|
|
let response = recipe_info.final_output_response;
|
|
|
|
assert_eq!(input_config.contents, Some("test_prompt".to_string()));
|
|
assert_eq!(
|
|
input_config.additional_system_prompt,
|
|
Some("test_instructions my_value".to_string())
|
|
);
|
|
assert!(input_config.extensions_override.is_none());
|
|
|
|
assert!(settings.is_some());
|
|
let settings = settings.unwrap();
|
|
assert_eq!(settings.goose_provider, Some("test_provider".to_string()));
|
|
assert_eq!(settings.goose_model, Some("test_model".to_string()));
|
|
assert_eq!(settings.temperature, Some(0.7));
|
|
|
|
assert!(sub_recipes.is_some());
|
|
let sub_recipes = sub_recipes.unwrap();
|
|
assert!(sub_recipes.len() == 1);
|
|
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!(response.is_some());
|
|
let response = response.unwrap();
|
|
assert_eq!(
|
|
response.json_schema,
|
|
Some(serde_json::json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"result": {"type": "string"}
|
|
}
|
|
}))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_recipe_info_from_cli_with_additional_sub_recipes() {
|
|
let (_temp_dir, recipe_path) = create_recipe();
|
|
|
|
// Create actual sub-recipe files in the temp directory
|
|
std::fs::create_dir_all(_temp_dir.path().join("path/to")).unwrap();
|
|
std::fs::create_dir_all(_temp_dir.path().join("another")).unwrap();
|
|
|
|
let sub_recipe1_path = _temp_dir.path().join("path/to/sub_recipe1.yaml");
|
|
let sub_recipe2_path = _temp_dir.path().join("another/sub_recipe2.yaml");
|
|
|
|
std::fs::write(&sub_recipe1_path, "title: Sub Recipe 1").unwrap();
|
|
std::fs::write(&sub_recipe2_path, "title: Sub Recipe 2").unwrap();
|
|
|
|
let params = vec![("name".to_string(), "my_value".to_string())];
|
|
let recipe_name = recipe_path.to_str().unwrap().to_string();
|
|
let additional_sub_recipes = vec![
|
|
sub_recipe1_path.to_string_lossy().to_string(),
|
|
sub_recipe2_path.to_string_lossy().to_string(),
|
|
];
|
|
|
|
let (input_config, recipe_info) =
|
|
extract_recipe_info_from_cli(recipe_name, params, additional_sub_recipes).unwrap();
|
|
let settings = recipe_info.session_settings;
|
|
let sub_recipes = recipe_info.sub_recipes;
|
|
let response = recipe_info.final_output_response;
|
|
|
|
assert_eq!(input_config.contents, Some("test_prompt".to_string()));
|
|
assert_eq!(
|
|
input_config.additional_system_prompt,
|
|
Some("test_instructions my_value".to_string())
|
|
);
|
|
assert!(input_config.extensions_override.is_none());
|
|
|
|
assert!(settings.is_some());
|
|
let settings = settings.unwrap();
|
|
assert_eq!(settings.goose_provider, Some("test_provider".to_string()));
|
|
assert_eq!(settings.goose_model, Some("test_model".to_string()));
|
|
assert_eq!(settings.temperature, Some(0.7));
|
|
|
|
assert!(sub_recipes.is_some());
|
|
let sub_recipes = sub_recipes.unwrap();
|
|
assert!(sub_recipes.len() == 3);
|
|
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_eq!(
|
|
sub_recipes[1].path,
|
|
sub_recipe1_path
|
|
.canonicalize()
|
|
.unwrap()
|
|
.to_string_lossy()
|
|
.to_string()
|
|
);
|
|
assert_eq!(sub_recipes[1].name, "sub_recipe1".to_string());
|
|
assert!(sub_recipes[1].values.is_none());
|
|
assert_eq!(
|
|
sub_recipes[2].path,
|
|
sub_recipe2_path
|
|
.canonicalize()
|
|
.unwrap()
|
|
.to_string_lossy()
|
|
.to_string()
|
|
);
|
|
assert_eq!(sub_recipes[2].name, "sub_recipe2".to_string());
|
|
assert!(sub_recipes[2].values.is_none());
|
|
assert!(response.is_some());
|
|
let response = response.unwrap();
|
|
assert_eq!(
|
|
response.json_schema,
|
|
Some(serde_json::json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"result": {"type": "string"}
|
|
}
|
|
}))
|
|
);
|
|
}
|
|
|
|
fn create_recipe() -> (TempDir, PathBuf) {
|
|
let test_recipe_content = r#"
|
|
title: test_recipe
|
|
description: A test recipe
|
|
instructions: test_instructions {{name}}
|
|
prompt: test_prompt
|
|
parameters:
|
|
- key: name
|
|
description: name
|
|
input_type: string
|
|
requirement: required
|
|
settings:
|
|
goose_provider: test_provider
|
|
goose_model: test_model
|
|
temperature: 0.7
|
|
sub_recipes:
|
|
- path: existing_sub_recipe.yaml
|
|
name: existing_sub_recipe
|
|
response:
|
|
json_schema:
|
|
type: object
|
|
properties:
|
|
result:
|
|
type: string
|
|
"#;
|
|
let temp_dir = tempfile::tempdir().unwrap();
|
|
let recipe_path: std::path::PathBuf = temp_dir.path().join("test_recipe.yaml");
|
|
|
|
std::fs::write(&recipe_path, test_recipe_content).unwrap();
|
|
let canonical_recipe_path = recipe_path.canonicalize().unwrap();
|
|
(temp_dir, canonical_recipe_path)
|
|
}
|
|
}
|