import type { AgentToolDefinition } from "@/lib/llm"; import { MOOD_CATALOG } from "@/modules/journal/mood-catalog"; const JOURNAL_MOOD_IDS = MOOD_CATALOG.map((mood) => mood.id).join(", "); export const AGENT_TOOLS: AgentToolDefinition[] = [ { type: "function", function: { name: "list_lists", description: "List household lists. Optionally filter by type such as shopping or tasks.", parameters: { type: "object", properties: { type: { type: "string", description: "List type filter, e.g. shopping or tasks" }, }, }, }, }, { type: "function", function: { name: "list_list_items", description: "List items on a specific list by list id.", parameters: { type: "object", properties: { listId: { type: "string", description: "UUID of the list" }, }, required: ["listId"], }, }, }, { type: "function", function: { name: "add_list_item", description: "Add an item to a list. Provide listId, or listType (e.g. shopping) to use the first matching list.", parameters: { type: "object", properties: { listId: { type: "string", description: "UUID of the list" }, listType: { type: "string", description: "List type when listId is unknown" }, text: { type: "string", description: "Item text" }, qty: { type: "string", description: "Optional quantity" }, }, required: ["text"], }, }, }, { type: "function", function: { name: "update_list_item", description: "Update a list item — mark done/undone, rename, or change quantity. Use list_list_items first to resolve itemId.", parameters: { type: "object", properties: { listId: { type: "string", description: "UUID of the list" }, itemId: { type: "string", description: "UUID of the item" }, done: { type: "boolean", description: "Mark completed (true) or open (false)" }, text: { type: "string", description: "New item text" }, qty: { type: "string", description: "New quantity" }, }, required: ["listId", "itemId"], }, }, }, { type: "function", function: { name: "delete_list_item", description: "Remove an item from a list permanently.", parameters: { type: "object", properties: { listId: { type: "string", description: "UUID of the list" }, itemId: { type: "string", description: "UUID of the item" }, }, required: ["listId", "itemId"], }, }, }, { type: "function", function: { name: "create_list", description: "Create a new list (e.g. type shopping or tasks).", parameters: { type: "object", properties: { type: { type: "string", description: "List type, e.g. shopping or tasks" }, name: { type: "string", description: "Display name" }, }, required: ["type", "name"], }, }, }, { type: "function", function: { name: "update_list", description: "Rename a list or archive/unarchive it.", parameters: { type: "object", properties: { listId: { type: "string" }, name: { type: "string" }, archived: { type: "boolean" }, }, required: ["listId"], }, }, }, { type: "function", function: { name: "delete_list", description: "Permanently delete a list and all its items.", parameters: { type: "object", properties: { listId: { type: "string" }, }, required: ["listId"], }, }, }, { type: "function", function: { name: "list_calendars", description: "List calendars visible to the household.", parameters: { type: "object", properties: {} }, }, }, { type: "function", function: { name: "list_events", description: "List calendar events in a date range (ISO 8601 from/to).", parameters: { type: "object", properties: { from: { type: "string", description: "Range start (ISO 8601)" }, to: { type: "string", description: "Range end (ISO 8601)" }, calendarIds: { type: "string", description: 'Comma-separated calendar UUIDs, or "all"', }, }, required: ["from", "to"], }, }, }, { type: "function", function: { name: "create_event", description: "Create a calendar event. Provide calendarId, or calendarName to match by name, or omit both to use the first visible calendar.", parameters: { type: "object", properties: { calendarId: { type: "string", description: "UUID of the calendar" }, calendarName: { type: "string", description: "Calendar display name when calendarId is unknown", }, title: { type: "string" }, startAt: { type: "string", description: "ISO 8601 start" }, endAt: { type: "string", description: "ISO 8601 end" }, allDay: { type: "boolean" }, location: { type: "string" }, notes: { type: "string" }, remindMinutesBefore: { type: "number", description: "Optional reminder N minutes before start", }, }, required: ["title", "startAt", "endAt"], }, }, }, { type: "function", function: { name: "update_event", description: "Update or reschedule a calendar event.", parameters: { type: "object", properties: { eventId: { type: "string" }, title: { type: "string" }, startAt: { type: "string" }, endAt: { type: "string" }, allDay: { type: "boolean" }, location: { type: "string" }, notes: { type: "string" }, remindMinutesBefore: { type: "number" }, }, required: ["eventId"], }, }, }, { type: "function", function: { name: "delete_event", description: "Delete a calendar event.", parameters: { type: "object", properties: { eventId: { type: "string" }, }, required: ["eventId"], }, }, }, { type: "function", function: { name: "create_calendar", description: "Create a new calendar.", parameters: { type: "object", properties: { name: { type: "string" }, color: { type: "string" }, visibility: { type: "string", description: "private or household" }, }, required: ["name"], }, }, }, { type: "function", function: { name: "list_notes", description: "List household notes.", parameters: { type: "object", properties: {} }, }, }, { type: "function", function: { name: "create_note", description: "Create a note with title and optional body.", parameters: { type: "object", properties: { title: { type: "string" }, body: { type: "string" }, pinned: { type: "boolean" }, remindAt: { type: "string", description: "ISO 8601 reminder datetime, or null to clear" }, }, required: ["title"], }, }, }, { type: "function", function: { name: "update_note", description: "Update a note's title, body, pin state, or reminder.", parameters: { type: "object", properties: { noteId: { type: "string" }, title: { type: "string" }, body: { type: "string" }, pinned: { type: "boolean" }, remindAt: { type: "string", description: "ISO 8601 datetime or null" }, }, required: ["noteId"], }, }, }, { type: "function", function: { name: "delete_note", description: "Delete a note.", parameters: { type: "object", properties: { noteId: { type: "string" }, }, required: ["noteId"], }, }, }, { type: "function", function: { name: "list_journal_entries", description: "List the current user's journal entries (most recent first).", parameters: { type: "object", properties: { limit: { type: "number", description: "Max entries to return (default 20)" }, }, }, }, }, { type: "function", function: { name: "create_journal_entry", description: "Create a journal entry for the current user. Supports moods, stress (1-10), and pillsTaken.", parameters: { type: "object", properties: { recordedAt: { type: "string", description: "ISO 8601 date/time for the entry" }, title: { type: "string" }, body: { type: "string" }, moods: { type: "array", items: { type: "string" }, description: `Mood ids: ${JOURNAL_MOOD_IDS}`, }, stress: { type: "number", description: "Stress level 1-10" }, pillsTaken: { type: "boolean", description: "Whether pills were taken" }, }, required: ["recordedAt"], }, }, }, { type: "function", function: { name: "update_journal_entry", description: "Update a journal entry including moods, stress, and pillsTaken.", parameters: { type: "object", properties: { entryId: { type: "string" }, recordedAt: { type: "string" }, title: { type: "string" }, body: { type: "string" }, moods: { type: "array", items: { type: "string" }, description: `Mood ids: ${JOURNAL_MOOD_IDS}`, }, stress: { type: "number" }, pillsTaken: { type: "boolean" }, }, required: ["entryId"], }, }, }, { type: "function", function: { name: "delete_journal_entry", description: "Delete a journal entry.", parameters: { type: "object", properties: { entryId: { type: "string" }, }, required: ["entryId"], }, }, }, { type: "function", function: { name: "list_bangs", description: "Get bang counter stats and recent entries for the household.", parameters: { type: "object", properties: { limit: { type: "number", description: "Recent entries to include (default 10)" }, }, }, }, }, { type: "function", function: { name: "add_bang", description: "Record a new bang. Date defaults to today if omitted.", parameters: { type: "object", properties: { occurredOn: { type: "string", description: "YYYY-MM-DD" }, }, }, }, }, { type: "function", function: { name: "update_bang", description: "Change the date of an existing bang entry.", parameters: { type: "object", properties: { bangId: { type: "string" }, occurredOn: { type: "string", description: "YYYY-MM-DD" }, }, required: ["bangId", "occurredOn"], }, }, }, { type: "function", function: { name: "delete_bang", description: "Delete a bang entry.", parameters: { type: "object", properties: { bangId: { type: "string" }, }, required: ["bangId"], }, }, }, { type: "function", function: { name: "list_garden_containers", description: "List garden containers (pots, beds, etc.).", parameters: { type: "object", properties: {} }, }, }, { type: "function", function: { name: "list_garden_plants", description: "List plants. Optionally filter by containerId.", parameters: { type: "object", properties: { containerId: { type: "string", description: "UUID of container" }, }, }, }, }, { type: "function", function: { name: "get_garden_plant", description: "Get full detail for a plant by id.", parameters: { type: "object", properties: { plantId: { type: "string" } }, required: ["plantId"], }, }, }, { type: "function", function: { name: "create_garden_container", description: "Create a garden container.", parameters: { type: "object", properties: { name: { type: "string" }, type: { type: "string", description: "e.g. pot, bed, greenhouse" }, locationNotes: { type: "string" }, }, required: ["name"], }, }, }, { type: "function", function: { name: "update_garden_container", description: "Update a garden container.", parameters: { type: "object", properties: { containerId: { type: "string" }, name: { type: "string" }, type: { type: "string" }, locationNotes: { type: "string" }, }, required: ["containerId"], }, }, }, { type: "function", function: { name: "delete_garden_container", description: "Delete a garden container.", parameters: { type: "object", properties: { containerId: { type: "string" } }, required: ["containerId"], }, }, }, { type: "function", function: { name: "create_garden_plant", description: "Add a new plant to the garden.", parameters: { type: "object", properties: { name: { type: "string" }, category: { type: "string" }, containerId: { type: "string" }, healthStatus: { type: "string" }, notes: { type: "string" }, }, required: ["name"], }, }, }, { type: "function", function: { name: "update_garden_plant", description: "Update plant details (name, health, container, notes, etc.).", parameters: { type: "object", properties: { plantId: { type: "string" }, name: { type: "string" }, category: { type: "string" }, containerId: { type: "string" }, healthStatus: { type: "string" }, notes: { type: "string" }, }, required: ["plantId"], }, }, }, { type: "function", function: { name: "delete_garden_plant", description: "Delete a plant.", parameters: { type: "object", properties: { plantId: { type: "string" } }, required: ["plantId"], }, }, }, { type: "function", function: { name: "log_garden_care", description: "Record care performed on a plant (water, fertilize, prune, etc.).", parameters: { type: "object", properties: { plantId: { type: "string" }, careType: { type: "string", description: "e.g. water, fertilize, prune" }, notes: { type: "string" }, performedAt: { type: "string", description: "ISO 8601; defaults to now" }, }, required: ["plantId", "careType"], }, }, }, { type: "function", function: { name: "list_garden_care_logs", description: "List recent care logs for a plant.", parameters: { type: "object", properties: { plantId: { type: "string" }, limit: { type: "number" }, }, required: ["plantId"], }, }, }, { type: "function", function: { name: "list_garden_care_schedules", description: "List care schedules (recurring reminders) for a plant.", parameters: { type: "object", properties: { plantId: { type: "string" } }, required: ["plantId"], }, }, }, { type: "function", function: { name: "upsert_garden_care_schedule", description: "Create or update a recurring care schedule for a plant.", parameters: { type: "object", properties: { plantId: { type: "string" }, careType: { type: "string" }, intervalDays: { type: "number", description: "Days between care" }, enabled: { type: "boolean" }, }, required: ["plantId", "careType", "intervalDays"], }, }, }, { type: "function", function: { name: "delete_garden_care_schedule", description: "Delete a care schedule by schedule id.", parameters: { type: "object", properties: { scheduleId: { type: "string" } }, required: ["scheduleId"], }, }, }, { type: "function", function: { name: "toggle_garden_care_schedule", description: "Enable or disable a care schedule.", parameters: { type: "object", properties: { scheduleId: { type: "string" }, enabled: { type: "boolean" }, }, required: ["scheduleId", "enabled"], }, }, }, { type: "function", function: { name: "push_overdue_garden_care", description: "Push overdue garden care items to the household task list.", parameters: { type: "object", properties: {} }, }, }, { type: "function", function: { name: "schedule_garden_care_on_calendar", description: "Add a care schedule's next due date as a calendar event.", parameters: { type: "object", properties: { scheduleId: { type: "string" }, calendarId: { type: "string" }, reminderMinutesBefore: { type: "number" }, }, required: ["scheduleId", "calendarId"], }, }, }, { type: "function", function: { name: "list_shareable_entity_types", description: "List entity types that can be shared via public links.", parameters: { type: "object", properties: {} }, }, }, { type: "function", function: { name: "list_share_links", description: "List active share links. Optionally filter by entityType and entityId.", parameters: { type: "object", properties: { entityType: { type: "string" }, entityId: { type: "string" }, }, }, }, }, { type: "function", function: { name: "create_share_link", description: "Create a temporary public share link for an entity. Entity types: calendar, calendar.event, list, note, garden.plant, garden.container.", parameters: { type: "object", properties: { entityType: { type: "string" }, entityId: { type: "string" }, expiresAt: { type: "string", description: "ISO 8601 expiry, or omit for no expiry" }, write: { type: "boolean", description: "Allow write via link (default false)" }, }, required: ["entityType", "entityId"], }, }, }, { type: "function", function: { name: "revoke_share_link", description: "Revoke an active share link by its id.", parameters: { type: "object", properties: { linkId: { type: "string" } }, required: ["linkId"], }, }, }, { type: "function", function: { name: "get_api_docs", description: "Search famapp REST API docs for /api/v1 paths. Always pass search. Returns matching lines only — not the full OpenAPI file.", parameters: { type: "object", properties: { search: { type: "string", description: "Keyword to filter paths (e.g. garden, share, journal, events)", }, }, required: ["search"], }, }, }, { type: "function", function: { name: "call_api", description: "Fallback: call any /api/v1/* endpoint directly. Use get_api_docs first when unsure. Only household-scoped v1 routes.", parameters: { type: "object", properties: { method: { type: "string", enum: ["GET", "POST", "PATCH", "DELETE"], description: "HTTP method", }, path: { type: "string", description: "API path starting with /api/v1/ (e.g. /api/v1/notes)", }, query: { type: "object", description: "Optional query string parameters as key-value pairs", }, body: { type: "object", description: "Optional JSON body for POST/PATCH", }, }, required: ["method", "path"], }, }, }, ]; export const AGENT_SYSTEM_PROMPT = `You are the famapp household assistant. Help Matt and his wife manage calendar events, shopping and task lists, notes, journal entries, garden plants, share links, and the bang counter. Use the provided tools to read and update data. Prefer calling tools instead of guessing. Be concise and friendly. Prefer a dedicated tool when one exists (create_event, add_list_item, create_note, etc.). After a successful write (2xx), stop calling tools and confirm in one short sentence — do not re-list or re-create. When no dedicated tool fits, or you need an endpoint that is not wrapped yet: 1. Call get_api_docs with a relevant search term (always pass search; never request the full spec). 2. Call call_api with the documented method, path, query, and body. If a tool call fails, read the error body and fix the arguments before trying a different approach. Do not repeat the exact same tool call. When the user sends a photo, read dates, times, locations, and action items from it, then use tools to act. Lists: resolve list ids via list_lists, or pass listType to add_list_item. To complete items, list_list_items then update_list_item with done: true. Journal: per-user private entries. Valid mood ids: ${JOURNAL_MOOD_IDS}. stress is 1-10. pillsTaken is boolean. Garden: care types are free text (water, fertilize, prune, etc.). Use list_garden_plants to find plant ids. Sharing: journal entries are not shareable. Shareable types: calendar, calendar.event, list, note, garden.plant, garden.container. Calendar: use ISO 8601 datetimes with the household timezone below. Pass calendarId, or calendarName, or omit both to use the first visible calendar. Bang dates use YYYY-MM-DD.`; export function resolveHouseholdTimezone(): string { return process.env.HOUSEHOLD_TIMEZONE?.trim() || process.env.TZ?.trim() || "America/Chicago"; } export function appendAgentRuntimeContext(prompt: string, now: Date = new Date()): string { const timeZone = resolveHouseholdTimezone(); let localNow: string; try { localNow = new Intl.DateTimeFormat("en-US", { timeZone, weekday: "long", year: "numeric", month: "long", day: "numeric", hour: "numeric", minute: "2-digit", hour12: true, timeZoneName: "short", }).format(now); } catch { localNow = now.toISOString(); } return `${prompt.trim()} Current time: ${localNow} (${timeZone}). ISO now: ${now.toISOString()}. Resolve relative dates from this clock.`; }