CI / checks (push) Has been cancelled
Cap get_api_docs, break duplicate tool rounds, force a reply after successful writes, and log each tool call so limit hits are diagnosable.
816 lines
23 KiB
TypeScript
816 lines
23 KiB
TypeScript
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.`;
|
|
}
|