import { defineAction } from "@agent-native/core/action"; import { z } from "zod"; import { withActionSpan } from "../server/otel.js"; import { fetchAllQuoteRows, hydrateQuotes, parseTags } from "../server/lib/quotes.js"; export default defineAction({ description: "List/browse poem quotes, optionally filtered by tag, author, or a free-text search over the quote and poem title. Set favoritesOnly=\"true\" with visitorId to list only that visitor's favorites (prefer list-favorites for that case though).", schema: z.object({ tag: z.string().optional().describe("Only quotes with this tag (e.g. \"hope\", \"nature\")"), author: z.string().optional().describe("Only quotes by this poet (exact match)"), search: z .string() .optional() .describe("Free-text search across quote text and poem title"), visitorId: z .string() .optional() .describe("Anonymous visitor id, used to mark which quotes this visitor has favorited"), favoritesOnly: z .string() .optional() .describe("Pass \"true\" (with visitorId) to only return favorited quotes"), limit: z.coerce .number() .int() .min(1) .max(200) .optional() .describe("Max quotes to return (default 100)"), }), http: { method: "GET" }, run: async (args) => { return withActionSpan( "list-quotes", { tag: args.tag, author: args.author, search: args.search }, async () => { const rows = await fetchAllQuoteRows(); const search = args.search?.trim().toLowerCase(); const filtered = rows.filter((row) => { if (args.author && row.poem.author !== args.author) return false; if (args.tag && !parseTags(row.quote.tags_json).includes(args.tag)) { return false; } if (search) { const haystack = `${row.quote.text} ${row.poem.title} ${row.poem.author}`.toLowerCase(); if (!haystack.includes(search)) return false; } return true; }); filtered.sort((a, b) => (b.quote.created_at ?? "").localeCompare(a.quote.created_at ?? "")); const limited = filtered.slice(0, args.limit ?? 100); let hydrated = await hydrateQuotes(limited, args.visitorId); if (args.favoritesOnly === "true") { hydrated = hydrated.filter((q) => q.isFavorited); } return hydrated; }, ); }, });