Detailed changes
@@ -404,6 +404,12 @@ func newRecipeSaveResult(recipeID string) (*sdk.CallToolResult, SaveRecipeOutput
return newToolResult(formatRecipeSave(output), output), output, nil
}
+func newRecipeOverwriteResult(recipeID string) (*sdk.CallToolResult, SaveRecipeOutput, error) {
+ output := SaveRecipeOutput{RecipeID: recipeID}
+
+ return newToolResult(formatRecipeOverwrite(output), output), output, nil
+}
+
func (s *Server) callDeleteRecipeTool(
ctx context.Context,
_ *sdk.CallToolRequest,
@@ -1039,6 +1045,10 @@ func formatRecipeSave(output SaveRecipeOutput) string {
return "Saved recipe (id: " + output.RecipeID + ")."
}
+func formatRecipeOverwrite(output SaveRecipeOutput) string {
+ return "Saved draft into existing recipe (id: " + output.RecipeID + ")."
+}
+
func formatRecipeURLImportDraft(output SaveRecipeOutput) string {
title := output.Title
if strings.TrimSpace(title) == "" {
@@ -1124,7 +1134,7 @@ type ExtractIntoRecipeOutput struct {
// SaveRecipeArguments contains save_recipe tool arguments.
type SaveRecipeArguments struct {
Source string `json:"source" jsonschema:"Recipe save source: raw_text, prepared, url, draft, or existing."`
- RecipeID string `json:"recipe_id,omitempty" jsonschema:"Recipe ID for existing recipe updates."`
+ RecipeID string `json:"recipe_id,omitempty" jsonschema:"Recipe ID for source=existing updates or source=draft overwrite into an existing recipe."`
DraftID string `json:"draft_id,omitempty" jsonschema:"Draft ID for reviewed URL import draft saves."`
Title string `json:"title,omitempty" jsonschema:"Recipe title for raw_text or prepared saves."`
Text string `json:"text,omitempty" jsonschema:"Raw recipe text for raw_text saves."`
@@ -39,13 +39,22 @@ func (s *Server) callDraftSaveRecipeTool(
portions = content.Portions
}
- recipeID, err := s.backend.SaveRecipeDraft(ctx, draftID, markdown, portions)
+ recipeID := strings.TrimSpace(arguments.RecipeID)
+ if recipeID != "" {
+ if err := s.backend.UpdateRecipeContent(ctx, recipeID, markdown, portions); err != nil {
+ return nil, SaveRecipeOutput{}, err
+ }
+
+ return newRecipeOverwriteResult(recipeID)
+ }
+
+ savedRecipeID, err := s.backend.SaveRecipeDraft(ctx, draftID, markdown, portions)
if err != nil {
return nil, SaveRecipeOutput{}, err
}
- if strings.TrimSpace(recipeID) == "" {
+ if strings.TrimSpace(savedRecipeID) == "" {
return nil, SaveRecipeOutput{}, fmt.Errorf("save recipe draft response missing recipe ID")
}
- return newRecipeSaveResult(recipeID)
+ return newRecipeSaveResult(savedRecipeID)
}
@@ -58,6 +58,46 @@ func TestCallToolACIDRecipesSave7And9And16SavesReviewedDraft(t *testing.T) {
}
}
+// recipes.SAVE.7-5 recipes.SAVE.9-1 recipes.SAVE.16 recipes.SAFETY.1 recipes.SAFETY.2
+func TestCallToolACIDRecipesSave7_5And9_1OverwritesExistingRecipeFromDraft(t *testing.T) {
+ backend := &fakeBackend{}
+ server := NewServer(backend, "test")
+
+ _, output, err := server.callSaveRecipeTool(
+ context.Background(),
+ nil,
+ SaveRecipeArguments{
+ Source: " draft ",
+ DraftID: " draft-1 ",
+ RecipeID: " recipe-1 ",
+ Markdown: " # Pasta\n\n1. Boil pasta.\n",
+ Portions: 2,
+ },
+ )
+ if err != nil {
+ t.Fatalf("callSaveRecipeTool() error = %v", err)
+ }
+
+ if backend.updateCalls != 1 {
+ t.Fatalf("update calls = %d, want 1", backend.updateCalls)
+ }
+ if backend.updateRecipeID != "recipe-1" {
+ t.Fatalf("backend update recipe ID = %q, want recipe-1", backend.updateRecipeID)
+ }
+ if backend.updateMarkdown != " # Pasta\n\n1. Boil pasta.\n" {
+ t.Fatalf("backend update markdown = %q, want raw markdown preserved", backend.updateMarkdown)
+ }
+ if backend.updatePortions != 2 {
+ t.Fatalf("backend update portions = %g, want 2", backend.updatePortions)
+ }
+ if output.RecipeID != "recipe-1" {
+ t.Fatalf("save output recipe ID = %q, want recipe-1", output.RecipeID)
+ }
+ if backend.draftSaveCalls != 0 || backend.saveCalls != 0 {
+ t.Fatalf("draft save/prepared calls = %d/%d, want none", backend.draftSaveCalls, backend.saveCalls)
+ }
+}
+
// recipes.SAVE.7 recipes.SAVE.7-3 recipes.SAVE.9 recipes.SAVE.16 tools.SAVE_RECIPE_TOOL.5
func TestCallToolACIDRecipesSave7_3SavesCurrentDraftContentWhenContentOmitted(t *testing.T) {
backend := &fakeBackend{