/** * Canvas Tool Registry — shared by server (Gemini function declarations) and client (shape spawning). * Pure TypeScript, no DOM or server dependencies. */ export interface CanvasToolDefinition { declaration: { name: string; description: string; parameters: { type: "object"; properties: Record; required: string[]; }; }; tagName: string; buildProps: (args: Record) => Record; actionLabel: (args: Record) => string; } const registry: CanvasToolDefinition[] = [ { declaration: { name: "create_map", description: "Create an interactive map centered on a location. Use when the user wants to see a place, get directions, or explore a geographic area.", parameters: { type: "object", properties: { latitude: { type: "number", description: "Latitude of the center point" }, longitude: { type: "number", description: "Longitude of the center point" }, zoom: { type: "number", description: "Zoom level (1-18, default 12)" }, location_name: { type: "string", description: "Human-readable name of the location" }, }, required: ["latitude", "longitude", "location_name"], }, }, tagName: "folk-map", buildProps: (args) => ({ center: [args.longitude, args.latitude], zoom: args.zoom || 12, }), actionLabel: (args) => `Created map: ${args.location_name}`, }, { declaration: { name: "create_note", description: "Create a markdown note on the canvas. Use for text content, lists, summaries, instructions, or any written information.", parameters: { type: "object", properties: { content: { type: "string", description: "Markdown content for the note" }, title: { type: "string", description: "Optional title for the note" }, }, required: ["content"], }, }, tagName: "folk-markdown", buildProps: (args) => ({ value: args.title ? `# ${args.title}\n\n${args.content}` : args.content, }), actionLabel: (args) => `Created note${args.title ? `: ${args.title}` : ""}`, }, { declaration: { name: "create_embed", description: "Embed a webpage or web app on the canvas. Use for websites, search results, booking sites, videos, or any URL the user wants to view inline.", parameters: { type: "object", properties: { url: { type: "string", description: "The URL to embed" }, title: { type: "string", description: "Descriptive title for the embed" }, }, required: ["url"], }, }, tagName: "folk-embed", buildProps: (args) => ({ url: args.url, }), actionLabel: (args) => `Embedded: ${args.title || args.url}`, }, { declaration: { name: "create_image", description: "Display an image on the canvas from a URL. Use when showing an existing image, photo, diagram, or any direct image link.", parameters: { type: "object", properties: { src: { type: "string", description: "Image URL" }, alt: { type: "string", description: "Alt text describing the image" }, }, required: ["src"], }, }, tagName: "folk-image", buildProps: (args) => ({ src: args.src, alt: args.alt || "", }), actionLabel: (args) => `Created image${args.alt ? `: ${args.alt}` : ""}`, }, { declaration: { name: "create_bookmark", description: "Create a bookmark card for a URL. Use when the user wants to save or reference a link without embedding the full page.", parameters: { type: "object", properties: { url: { type: "string", description: "The URL to bookmark" }, }, required: ["url"], }, }, tagName: "folk-bookmark", buildProps: (args) => ({ url: args.url, }), actionLabel: (args) => `Bookmarked: ${args.url}`, }, { declaration: { name: "create_image_gen", description: "Generate an AI image from a text prompt. Use when the user wants to create, generate, or imagine a new image that doesn't exist yet.", parameters: { type: "object", properties: { prompt: { type: "string", description: "Text prompt describing the image to generate" }, style: { type: "string", description: "Visual style for the generated image", enum: ["photorealistic", "illustration", "painting", "sketch", "punk-zine", "collage", "vintage", "minimalist"], }, }, required: ["prompt"], }, }, tagName: "folk-image-gen", buildProps: (args) => ({ prompt: args.prompt, style: args.style || "photorealistic", }), actionLabel: (args) => `Generating image: ${args.prompt.slice(0, 50)}${args.prompt.length > 50 ? "..." : ""}`, }, // ── Trip Planning Tools ── { declaration: { name: "create_destination", description: "Create a destination card for a trip location. Use when the user mentions a city, place, or stop on their trip.", parameters: { type: "object", properties: { destName: { type: "string", description: "Name of the destination (city or place)" }, country: { type: "string", description: "Country name" }, lat: { type: "number", description: "Latitude coordinate" }, lng: { type: "number", description: "Longitude coordinate" }, arrivalDate: { type: "string", description: "Arrival date in YYYY-MM-DD format" }, departureDate: { type: "string", description: "Departure date in YYYY-MM-DD format" }, notes: { type: "string", description: "Additional notes about this destination" }, }, required: ["destName"], }, }, tagName: "folk-destination", buildProps: (args) => ({ destName: args.destName, ...(args.country ? { country: args.country } : {}), ...(args.lat != null ? { lat: args.lat } : {}), ...(args.lng != null ? { lng: args.lng } : {}), ...(args.arrivalDate ? { arrivalDate: args.arrivalDate } : {}), ...(args.departureDate ? { departureDate: args.departureDate } : {}), ...(args.notes ? { notes: args.notes } : {}), }), actionLabel: (args) => `Created destination: ${args.destName}${args.country ? `, ${args.country}` : ""}`, }, { declaration: { name: "create_itinerary", description: "Create an itinerary card with a list of activities/events organized by date. Use when planning a schedule or day-by-day plan.", parameters: { type: "object", properties: { tripTitle: { type: "string", description: "Title for the itinerary" }, itemsJson: { type: "string", description: 'JSON array of items. Each: {"id":"","title":"...","date":"YYYY-MM-DD","startTime":"HH:MM","category":"ACTIVITY|TRANSPORT|MEAL|FREE_TIME|FLIGHT"}' }, }, required: ["tripTitle", "itemsJson"], }, }, tagName: "folk-itinerary", buildProps: (args) => { let items: any[] = []; try { items = JSON.parse(args.itemsJson); } catch { items = []; } return { tripTitle: args.tripTitle, items }; }, actionLabel: (args) => `Created itinerary: ${args.tripTitle}`, }, { declaration: { name: "create_booking", description: "Create a booking card for a flight, hotel, transport, activity, or restaurant reservation.", parameters: { type: "object", properties: { bookingType: { type: "string", description: "Type of booking", enum: ["FLIGHT", "HOTEL", "CAR_RENTAL", "TRAIN", "BUS", "FERRY", "ACTIVITY", "RESTAURANT", "OTHER"], }, provider: { type: "string", description: "Provider/company name (e.g. airline, hotel name)" }, cost: { type: "number", description: "Cost amount" }, currency: { type: "string", description: "ISO currency code (e.g. USD, EUR)" }, startDate: { type: "string", description: "Start/check-in date in YYYY-MM-DD format" }, endDate: { type: "string", description: "End/check-out date in YYYY-MM-DD format" }, bookingStatus: { type: "string", description: "Booking status", enum: ["PENDING", "CONFIRMED", "CANCELLED"] }, details: { type: "string", description: "Additional booking details or notes" }, }, required: ["bookingType", "provider"], }, }, tagName: "folk-booking", buildProps: (args) => ({ bookingType: args.bookingType, provider: args.provider, ...(args.cost != null ? { cost: args.cost } : {}), ...(args.currency ? { currency: args.currency } : {}), ...(args.startDate ? { startDate: args.startDate } : {}), ...(args.endDate ? { endDate: args.endDate } : {}), ...(args.bookingStatus ? { bookingStatus: args.bookingStatus } : {}), ...(args.details ? { details: args.details } : {}), }), actionLabel: (args) => `Created booking: ${args.bookingType} — ${args.provider}`, }, { declaration: { name: "create_budget", description: "Create a budget tracker card with total budget and expense line items. Use when the user wants to track trip costs.", parameters: { type: "object", properties: { budgetTotal: { type: "number", description: "Total budget amount" }, currency: { type: "string", description: "ISO currency code (e.g. USD, EUR)" }, expensesJson: { type: "string", description: 'JSON array of expenses. Each: {"id":"","category":"TRANSPORT|ACCOMMODATION|FOOD|ACTIVITY|SHOPPING|OTHER","description":"...","amount":123,"date":"YYYY-MM-DD"}' }, }, required: ["budgetTotal"], }, }, tagName: "folk-budget", buildProps: (args) => { let expenses: any[] = []; try { expenses = JSON.parse(args.expensesJson); } catch { expenses = []; } return { budgetTotal: args.budgetTotal, ...(args.currency ? { currency: args.currency } : {}), expenses, }; }, actionLabel: (args) => `Created budget: ${args.currency || "USD"} ${args.budgetTotal}`, }, { declaration: { name: "create_packing_list", description: "Create a packing list card with checkable items organized by category. Use when the user needs help with what to pack.", parameters: { type: "object", properties: { itemsJson: { type: "string", description: 'JSON array of packing items. Each: {"id":"","name":"...","category":"CLOTHING|FOOTWEAR|ELECTRONICS|GEAR|PERSONAL|DOCUMENTS|SAFETY|SUPPLIES","quantity":1,"packed":false}' }, }, required: ["itemsJson"], }, }, tagName: "folk-packing-list", buildProps: (args) => { let items: any[] = []; try { items = JSON.parse(args.itemsJson); } catch { items = []; } return { items }; }, actionLabel: (args) => { let count = 0; try { count = JSON.parse(args.itemsJson).length; } catch {} return `Created packing list (${count} items)`; }, }, ]; export const CANVAS_TOOLS: CanvasToolDefinition[] = [...registry]; export const CANVAS_TOOL_DECLARATIONS = CANVAS_TOOLS.map((t) => t.declaration); export function findTool(name: string): CanvasToolDefinition | undefined { return CANVAS_TOOLS.find((t) => t.declaration.name === name); } export function registerCanvasTool(def: CanvasToolDefinition): void { CANVAS_TOOLS.push(def); }