/** * 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 ? "..." : ""}`, }, ]; 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); }