Skip to content
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
a9d4be5
Initial refactoring to shared create form
zanesq Sep 21, 2025
3365a36
Fix recipe not cleared when starting from home and no messages sent
zanesq Sep 21, 2025
4bd54e2
clean up recipe form fields display, formatting and messaging
zanesq Sep 21, 2025
e5911b6
fix enable save recipe button
zanesq Sep 21, 2025
e6ecc6f
update form text
zanesq Sep 21, 2025
1ed7797
update action icons and added deeplink copy to clipboard
zanesq Sep 21, 2025
ad72537
hide create recipe while analyzing/creating recipe in backend
zanesq Sep 21, 2025
158e94a
revert setmessages removal for now
zanesq Sep 22, 2025
c6f80a5
remove console debugs
zanesq Sep 22, 2025
1449bf0
Merge branch 'main' of github.com:block/goose into zane/create-recipe…
zanesq Sep 23, 2025
f05ade7
Merge branch 'main' of github.com:block/goose into zane/create-recipe…
zanesq Sep 23, 2025
ad67dff
fix recipe instructions from session metadata not being injected
zanesq Sep 24, 2025
c66d934
Merge branch 'main' of github.com:block/goose into zane/create-recipe…
zanesq Sep 24, 2025
ba9b4f3
fix recipe issues from upstream changes and regenerate types
zanesq Sep 24, 2025
866bdc6
Save recipe parameter values to session and value substitution with r…
zanesq Sep 24, 2025
7459c55
fix session_metadata missing
zanesq Sep 24, 2025
667065d
fix session_metadata missing
zanesq Sep 24, 2025
ae1866a
Merge branch 'main' of github.com:block/goose into zane/create-recipe…
zanesq Sep 25, 2025
10cd55a
trigger param addition for activities onblur and extend check for new…
zanesq Sep 25, 2025
b0dd839
merged in main
zanesq Sep 27, 2025
0ebc710
cleanup
zanesq Sep 27, 2025
525d43e
rename ViewRecipeModal to CreateEditRecipeModal
zanesq Sep 27, 2025
20227e3
Merge branch 'main' of github.com:block/goose into zane/create-recipe…
zanesq Sep 30, 2025
7c04df5
delete empty file from merge deleted upstream
zanesq Sep 30, 2025
dacc13d
change to or_default
zanesq Oct 1, 2025
f6d7d24
just return 500 error
zanesq Oct 1, 2025
7b39480
remove comment
zanesq Oct 1, 2025
78e23ed
remove comment
zanesq Oct 1, 2025
a55cba7
add throwOnError
zanesq Oct 1, 2025
4f65ec5
move to finally
zanesq Oct 1, 2025
518917d
rename recipeConfig to recipe
zanesq Oct 1, 2025
d782390
remove unnecessary array check and change to flatmap
zanesq Oct 1, 2025
93bb300
remove shallow api wrapper
zanesq Oct 1, 2025
7b4fd5d
tests less brittle
zanesq Oct 2, 2025
17af21e
tests less brittle put back isArray
zanesq Oct 2, 2025
fe28b24
Merge branch 'main' of github.com:block/goose into zane/create-recipe…
zanesq Oct 2, 2025
1841373
await promise ref before clearing
zanesq Oct 2, 2025
713e761
rename recipe_parameters to user_recipe_values
zanesq Oct 2, 2025
d2af3ec
remove unnecessary isArray check
zanesq Oct 2, 2025
2b182d4
Merge branch 'main' of github.com:block/goose into zane/create-recipe…
zanesq Oct 2, 2025
9ca67c4
merging in main
zanesq Oct 2, 2025
13ffa45
merge in main
zanesq Oct 3, 2025
c40b5e2
change to not use save dialog for create and edit and add save and la…
zanesq Oct 3, 2025
9c4c587
revert changes to launch recipe in same window
zanesq Oct 3, 2025
2339804
merge in main
zanesq Oct 3, 2025
945261e
remove extra line from merge
zanesq Oct 3, 2025
7847f3c
rename recipeConfig to recipe everywhere
zanesq Oct 4, 2025
dac5617
put back setChat state
zanesq Oct 4, 2025
67aab50
Merge branch 'main' of github.com:block/goose into zane/create-recipe…
zanesq Oct 4, 2025
d50c0e5
finish renaming recipeConfig to recipe
zanesq Oct 5, 2025
6707948
remove unnecessary run recipe secondary state from create from session
zanesq Oct 5, 2025
57c3dd3
fix recipe name not pre-filled in edit form
zanesq Oct 5, 2025
ff19530
update test
zanesq Oct 5, 2025
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
2 changes: 1 addition & 1 deletion crates/goose-cli/src/session/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
process::exit(1);
});

// Handle session file resolution and resuming
// Handle session resolution and resuming
let session_id: Option<String> = if session_config.no_session {
None
} else if session_config.resume {
Expand Down
3 changes: 2 additions & 1 deletion crates/goose-server/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use goose::config::ExtensionEntry;
use goose::conversation::Conversation;
use goose::permission::permission_confirmation::PrincipalType;
use goose::providers::base::{ConfigKey, ModelInfo, ProviderMetadata};

use goose::session::{Session, SessionInsights};
use rmcp::model::{
Annotations, Content, EmbeddedResource, Icon, ImageContent, JsonObject, RawAudioContent,
Expand Down Expand Up @@ -353,6 +352,7 @@ derive_utoipa!(Icon as IconSchema);
super::routes::session::get_session_insights,
super::routes::session::update_session_description,
super::routes::session::delete_session,
super::routes::session::update_session_user_recipe_values,
super::routes::schedule::create_schedule,
super::routes::schedule::list_schedules,
super::routes::schedule::delete_schedule,
Expand Down Expand Up @@ -391,6 +391,7 @@ derive_utoipa!(Icon as IconSchema);
super::routes::context::ContextManageResponse,
super::routes::session::SessionListResponse,
super::routes::session::UpdateSessionDescriptionRequest,
super::routes::session::UpdateSessionUserRecipeValuesRequest,
Message,
MessageContent,
MessageMetadata,
Expand Down
53 changes: 32 additions & 21 deletions crates/goose-server/src/routes/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use std::sync::Arc;

use axum::routing::get;
use axum::{extract::State, http::StatusCode, routing::post, Json, Router};
use goose::conversation::{message::Message, Conversation};
use goose::recipe::recipe_library;
use goose::recipe::Recipe;
use goose::recipe_deeplink;
use goose::session::SessionManager;

use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
Expand All @@ -18,16 +18,10 @@ use crate::state::AppState;

#[derive(Debug, Deserialize, ToSchema)]
pub struct CreateRecipeRequest {
messages: Vec<Message>,
// Required metadata
title: String,
description: String,
session_id: String,
// Optional fields
#[serde(default)]
activities: Option<Vec<String>>,
#[serde(default)]
author: Option<AuthorRequest>,
session_id: String,
}

#[derive(Debug, Deserialize, ToSchema)]
Expand Down Expand Up @@ -127,25 +121,38 @@ async fn create_recipe(
Json(request): Json<CreateRecipeRequest>,
) -> Result<Json<CreateRecipeResponse>, StatusCode> {
tracing::info!(
"Recipe creation request received with {} messages",
request.messages.len()
"Recipe creation request received for session_id: {}",
request.session_id
);

// Load messages from session
let session = match SessionManager::get_session(&request.session_id, true).await {
Ok(session) => session,
Err(e) => {
tracing::error!("Failed to get session: {}", e);
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
};

let conversation = match session.conversation {
Some(conversation) => conversation,
None => {
let error_message = "Session has no conversation".to_string();
let error_response = CreateRecipeResponse {
recipe: None,
error: Some(error_message),
};
return Ok(Json(error_response));
}
};

let agent = state.get_agent_for_route(request.session_id).await?;

// Create base recipe from agent state and messages
let recipe_result = agent
.create_recipe(Conversation::new_unvalidated(request.messages))
.await;
let recipe_result = agent.create_recipe(conversation).await;

match recipe_result {
Ok(mut recipe) => {
recipe.title = request.title;
recipe.description = request.description;
if request.activities.is_some() {
recipe.activities = request.activities
};

if let Some(author_req) = request.author {
recipe.author = Some(goose::recipe::Author {
contact: author_req.contact,
Expand All @@ -160,7 +167,11 @@ async fn create_recipe(
}
Err(e) => {
tracing::error!("Error details: {:?}", e);
Err(StatusCode::BAD_REQUEST)
let error_response = CreateRecipeResponse {
recipe: None,
error: Some(format!("Failed to create recipe: {}", e)),
};
Ok(Json(error_response))
}
}
}
Expand Down Expand Up @@ -241,7 +252,7 @@ async fn scan_recipe(
async fn list_recipes(
State(state): State<Arc<AppState>>,
) -> Result<Json<ListRecipeResponse>, StatusCode> {
let recipe_manifest_with_paths = get_all_recipes_manifests().unwrap();
let recipe_manifest_with_paths = get_all_recipes_manifests().unwrap_or_default();
let mut recipe_file_hash_map = HashMap::new();
let recipe_manifest_responses = recipe_manifest_with_paths
.iter()
Expand Down
44 changes: 44 additions & 0 deletions crates/goose-server/src/routes/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use axum::{
use goose::session::session_manager::SessionInsights;
use goose::session::{Session, SessionManager};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use utoipa::ToSchema;

Expand All @@ -25,6 +26,13 @@ pub struct UpdateSessionDescriptionRequest {
description: String,
}

#[derive(Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateSessionUserRecipeValuesRequest {
/// Recipe parameter values entered by the user
user_recipe_values: HashMap<String, String>,
}

const MAX_DESCRIPTION_LENGTH: usize = 200;

#[utoipa::path(
Expand Down Expand Up @@ -128,6 +136,38 @@ async fn update_session_description(
Ok(StatusCode::OK)
}

#[utoipa::path(
put,
path = "/sessions/{session_id}/user_recipe_values",
request_body = UpdateSessionUserRecipeValuesRequest,
params(
("session_id" = String, Path, description = "Unique identifier for the session")
),
responses(
(status = 200, description = "Session user recipe values updated successfully"),
(status = 401, description = "Unauthorized - Invalid or missing API key"),
(status = 404, description = "Session not found"),
(status = 500, description = "Internal server error")
),
security(
("api_key" = [])
),
tag = "Session Management"
)]
// Update session user recipe parameter values
async fn update_session_user_recipe_values(
Path(session_id): Path<String>,
Json(request): Json<UpdateSessionUserRecipeValuesRequest>,
) -> Result<StatusCode, StatusCode> {
SessionManager::update_session(&session_id)
.user_recipe_values(Some(request.user_recipe_values))
.apply()
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

Ok(StatusCode::OK)
}

#[utoipa::path(
delete,
path = "/sessions/{session_id}",
Expand Down Expand Up @@ -169,5 +209,9 @@ pub fn routes(state: Arc<AppState>) -> Router {
"/sessions/{session_id}/description",
put(update_session_description),
)
.route(
"/sessions/{session_id}/user_recipe_values",
put(update_session_user_recipe_values),
)
.with_state(state)
}
26 changes: 24 additions & 2 deletions crates/goose/src/agents/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1591,9 +1591,31 @@ impl Agent {
extension_configs.len()
);

let (title, description) =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the comment is saying the same as the code

this is good though. I do wonder if we should move this to recipe.rs or some such. it really has no place in this file that already has > 2000 lines

if let Ok(json_content) = serde_json::from_str::<Value>(&clean_content) {
let title = json_content
.get("title")
.and_then(|t| t.as_str())
.unwrap_or("Custom recipe from chat")
.to_string();

let description = json_content
.get("description")
.and_then(|d| d.as_str())
.unwrap_or("a custom recipe instance from this chat session")
.to_string();

(title, description)
} else {
(
"Custom recipe from chat".to_string(),
"a custom recipe instance from this chat session".to_string(),
)
};

let recipe = Recipe::builder()
.title("Custom recipe from chat")
.description("a custom recipe instance from this chat session")
.title(title)
.description(description)
.instructions(instructions)
.activities(activities)
.extensions(extension_configs)
Expand Down
1 change: 1 addition & 0 deletions crates/goose/src/context_mgmt/auto_compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ mod tests {
extension_data: extension_data::ExtensionData::new(),
conversation: Some(conversation),
message_count,
user_recipe_values: None,
}
}

Expand Down
11 changes: 7 additions & 4 deletions crates/goose/src/prompts/recipe.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
Based on our conversation so far, could you create:

1. A concise set of instructions (1-2 paragraphs) that describe what you've been helping with. Make the instructions generic, and higher-level so that can be re-used across various similar tasks. Pay special attention if any output styles or formats are requested (and make it clear), and note any non standard tools used or required.
1. A concise title (5-10 words) that captures the main topic or task
2. A brief description (1-2 sentences) that summarizes what this recipe helps with
3. A concise set of instructions (1-2 paragraphs) that describe what you've been helping with. Make the instructions generic, and higher-level so that can be re-used across various similar tasks. Pay special attention if any output styles or formats are requested (and make it clear), and note any non standard tools used or required.
4. A list of 3-5 example activities (as a few words each at most) that would be relevant to this topic

2. A list of 3-5 example activities (as a few words each at most) that would be relevant to this topic

Format your response in _VALID_ json, with one key being `instructions` which contains a string, and the other key `activities` as an array of strings.
Format your response in _VALID_ json, with keys being `title`, `description`, `instructions` (string), and `activities` (array of strings).
For example, perhaps we have been discussing fruit and you might write:

{
"title": "Fruit Information Assistant",
"description": "A recipe for finding and sharing information about different types of fruit.",
"instructions": "Using web searches we find pictures of fruit, and always check what language to reply in.",
"activities": [
"Show pics of apples",
Expand Down
Loading
Loading