982d4e3264
Gives the general chat a createRecipe tool (Vercel AI SDK, stepCountIs(3)) scoped so it can only ever produce a draft — the tool's execute is a pure echo, no DB write. The route surfaces the tool call as proposedRecipe alongside the normal text answer; the chat UI renders it as a card with explicit Create/Discard buttons. Create POSTs to the existing /api/v1/recipes endpoint — the same code path and tier/recipe-count limit the manual editor already goes through — so there's exactly one place that actually creates a recipe row, and nothing happens without the user clicking Create. Scoped to the general assistant only (not per-recipe chat), and to one tool for now — addToShoppingList/generateMealPlan are follow-ups. v0.45.0
39 lines
1.9 KiB
TypeScript
39 lines
1.9 KiB
TypeScript
import { tool } from "ai";
|
|
import { z } from "zod";
|
|
|
|
const CreateRecipeInputSchema = z.object({
|
|
title: z.string().min(1).max(200),
|
|
description: z.string().max(2000).optional(),
|
|
baseServings: z.number().int().min(1).max(100).default(4),
|
|
recipeType: z.enum(["dish", "drink"]).default("dish"),
|
|
difficulty: z.enum(["easy", "medium", "hard"]).optional(),
|
|
prepMins: z.number().int().min(0).max(1440).optional(),
|
|
cookMins: z.number().int().min(0).max(1440).optional(),
|
|
ingredients: z.array(z.object({
|
|
rawName: z.string().min(1).max(200).describe("Ingredient name only, e.g. 'flour' — never combined with quantity/unit"),
|
|
quantity: z.number().optional().describe("A number only, e.g. 0.25, 1.5, 2"),
|
|
unit: z.string().max(50).optional().describe("e.g. 'cup', 'tbsp', 'g'"),
|
|
})).min(1).max(100),
|
|
steps: z.array(z.object({
|
|
instruction: z.string().min(1).max(2000),
|
|
})).min(1).max(100),
|
|
});
|
|
|
|
export type CreateRecipeToolInput = z.infer<typeof CreateRecipeInputSchema>;
|
|
|
|
/**
|
|
* Lets the cooking assistant propose a new recipe from the conversation.
|
|
* Deliberately does NOT write to the database — `execute` just validates and
|
|
* echoes the input back as a draft. The chat UI renders that draft with an
|
|
* explicit Confirm button, which POSTs to the existing /api/v1/recipes
|
|
* endpoint (the same one the manual recipe editor uses) — so nothing gets
|
|
* created without the user directly approving it, and there's exactly one
|
|
* code path that actually writes a recipe row.
|
|
*/
|
|
export const createRecipeTool = tool({
|
|
description:
|
|
"Propose creating a new recipe based on the conversation. This only drafts the recipe for the user to review — it does not save anything. Only call this when the user has actually asked you to create/save/write down a recipe, not just discussed one.",
|
|
inputSchema: CreateRecipeInputSchema,
|
|
execute: async (input: CreateRecipeToolInput) => input,
|
|
});
|