Agent skill

canvas-ai-tools

Implements AI tools for Canvas generation and updates. Use when creating GenUI tools for code generation, document editing, or Canvas interactions.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/canvas-ai-tools

SKILL.md

Canvas AI Tools Skill

When to Use

Use this skill when:

  • Creating AI tools that generate Canvas content
  • Implementing code generation/editing tools
  • Building GenUI tools for Canvas interaction
  • Adding AI-powered features to Canvas

GenUI Tool Architecture

lib/ai/tools/           # Tool definitions
├── canvas-tools.ts     # open_canvas, update_canvas, etc.
├── code-tools.ts       # run_code, explain_code
└── index.ts            # Registry

lib/ai/genui/           # UI components for tools
├── canvas-genui.tsx    # Canvas generation UI
└── code-result.tsx     # Code execution results

Canvas Tool Definitions

open_canvas Tool

typescript
import { z } from 'zod';
import { tool } from 'ai';
import { CANVAS_TYPES, CanvasConfigSchema } from '@/lib/canvas/types';

export const openCanvasTool = tool({
  description: `Opens an interactive canvas for code editing, visualization, or document creation.

  Use this when the student needs to:
  - Write, edit, or run code
  - Create visualizations
  - Work on documents
  - Practice with interactive exercises

  The canvas provides a Monaco editor with syntax highlighting and execution support.`,

  parameters: z.object({
    type: z.enum(CANVAS_TYPES).describe('Type of canvas to open'),
    title: z.string().max(100).describe('Title for the canvas'),
    language: z.string().optional().describe('Programming language (for code type)'),
    initialContent: z.string().optional().describe('Starting content'),
    generationPrompt: z.string().optional().describe('Internal prompt for generation'),
    educationalContext: z.object({
      topic: z.string().optional(),
      difficulty: z.enum(['beginner', 'intermediate', 'advanced']).optional(),
      learningObjective: z.string().optional(),
    }).optional(),
  }),

  execute: async (args) => {
    // Validate with full schema
    const config = CanvasConfigSchema.parse(args);

    return {
      action: 'open_canvas' as const,
      canvasConfig: config,
    };
  },
});

update_canvas Tool

typescript
export const updateCanvasTool = tool({
  description: `Updates the content of the currently open canvas.

  Use this when:
  - Fixing errors in student code
  - Adding examples or explanations
  - Modifying existing content based on student request

  Only updates specified fields, preserving others.`,

  parameters: z.object({
    content: z.string().optional().describe('New content for the canvas'),
    title: z.string().max(100).optional().describe('New title'),
    language: z.string().optional().describe('Change programming language'),
  }),

  execute: async (args) => {
    return {
      action: 'update_canvas' as const,
      updates: args,
    };
  },
});

run_code Tool

typescript
export const runCodeTool = tool({
  description: `Executes the current code in the canvas and returns the result.

  Supports:
  - Python (via Pyodide)
  - JavaScript/TypeScript (via iframe sandbox)
  - HTML/CSS (preview)

  Returns output, errors, and execution time.`,

  parameters: z.object({
    includeVariables: z.boolean().optional().describe('Include final variable values'),
  }),

  execute: async (args) => {
    return {
      action: 'run_code' as const,
      options: args,
    };
  },
});

Tool Registry Pattern

Registering Canvas Tools

typescript
// lib/ai/tools/registry.ts
import { openCanvasTool, updateCanvasTool, runCodeTool } from './canvas-tools';

export const CANVAS_TOOLS = {
  open_canvas: openCanvasTool,
  update_canvas: updateCanvasTool,
  run_code: runCodeTool,
} as const;

// For AI SDK
export const canvasToolsArray = Object.values(CANVAS_TOOLS);

Using in Chat

typescript
import { streamText } from 'ai';
import { CANVAS_TOOLS } from '@/lib/ai/tools/registry';

const result = await streamText({
  model,
  messages,
  tools: CANVAS_TOOLS,
  maxToolRoundtrips: 3,
});

GenUI Component Pattern

Tool Result Rendering

tsx
// lib/ai/genui/canvas-genui.tsx
'use client';

import { useEffect } from 'react';
import { useCanvasStore } from '@/stores/canvas-store';
import type { OpenCanvasResult, UpdateCanvasResult } from '@/lib/canvas/types';

interface CanvasGenUIProps {
  toolResult: OpenCanvasResult | UpdateCanvasResult;
}

export function CanvasGenUI({ toolResult }: CanvasGenUIProps) {
  const { openCanvas, updateCanvas } = useCanvasStore();

  useEffect(() => {
    if (toolResult.action === 'open_canvas') {
      openCanvas(toolResult.canvasConfig);
    } else if (toolResult.action === 'update_canvas') {
      updateCanvas(toolResult.updates);
    }
  }, [toolResult, openCanvas, updateCanvas]);

  // Return minimal UI - canvas opens in side panel
  return (
    <div className="flex items-center gap-2 text-sm text-muted-foreground">
      <div className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
      {toolResult.action === 'open_canvas'
        ? `Opening canvas: ${toolResult.canvasConfig.title}`
        : 'Updating canvas...'}
    </div>
  );
}

Code Execution Result

tsx
// lib/ai/genui/code-result.tsx
import { CheckCircle, XCircle, Clock } from 'lucide-react';
import { cn } from '@/lib/utils';

interface CodeResultProps {
  result: {
    success: boolean;
    output: string;
    error: string | null;
    durationMs: number;
  };
}

export function CodeResultGenUI({ result }: CodeResultProps) {
  return (
    <div className={cn(
      'rounded-lg border p-4',
      result.success ? 'bg-green-50 border-green-200' : 'bg-red-50 border-red-200'
    )}>
      <div className="flex items-center gap-2 mb-2">
        {result.success ? (
          <CheckCircle className="h-4 w-4 text-green-600" />
        ) : (
          <XCircle className="h-4 w-4 text-red-600" />
        )}
        <span className="font-medium">
          {result.success ? 'Execution Successful' : 'Execution Failed'}
        </span>
        <span className="text-xs text-muted-foreground ml-auto flex items-center gap-1">
          <Clock className="h-3 w-3" />
          {result.durationMs}ms
        </span>
      </div>

      {result.output && (
        <pre className="text-sm bg-white/50 rounded p-2 overflow-x-auto">
          {result.output}
        </pre>
      )}

      {result.error && (
        <pre className="text-sm text-red-700 bg-white/50 rounded p-2 overflow-x-auto">
          {result.error}
        </pre>
      )}
    </div>
  );
}

AI Tool Message Handling

Processing Tool Calls

tsx
// In chat message handler
import { useChat } from 'ai/react';
import { CanvasGenUI } from '@/lib/ai/genui/canvas-genui';

function ChatMessages() {
  const { messages } = useChat();

  return (
    <>
      {messages.map((message) => (
        <div key={message.id}>
          {/* Regular content */}
          {message.content}

          {/* Tool invocations */}
          {message.toolInvocations?.map((tool) => {
            if (tool.toolName === 'open_canvas' || tool.toolName === 'update_canvas') {
              return (
                <CanvasGenUI
                  key={tool.toolCallId}
                  toolResult={tool.result}
                />
              );
            }
            // Handle other tools...
          })}
        </div>
      ))}
    </>
  );
}

Code Generation Best Practices

Prompt Engineering for Code

typescript
const codeGenerationPrompt = `
Generate {language} code that:
1. Is educational and well-commented
2. Follows best practices for {language}
3. Is appropriate for {difficulty} level students
4. Demonstrates the concept: {concept}

Include:
- Clear variable names
- Step-by-step comments
- Example output as comments

Educational context:
- Topic: {topic}
- Learning objective: {learningObjective}
`;

Response Format

typescript
// AI should return structured code blocks
interface CodeGenerationResult {
  code: string;
  explanation: string;
  examples: string[];
  nextSteps: string[];
}

Tool Invocation Tracking

Artifact Persistence

typescript
// Track tool invocations for artifact persistence
interface CanvasArtifact {
  artifactId: string;
  conversationId: string;
  messageId: string;
  toolInvocationId: string;
  type: CanvasType;
  content: string;
  createdAt: Date;
  updatedAt: Date;
}

// Save artifact when canvas content changes
async function saveCanvasArtifact(
  conversationId: string,
  messageId: string,
  toolInvocationId: string,
  content: string
) {
  await db.insert(canvasArtifacts).values({
    id: nanoid(),
    conversationId,
    messageId,
    toolInvocationId,
    content,
    // ...
  });
}

Error Handling in Tools

Tool Error Response

typescript
export const openCanvasTool = tool({
  // ...
  execute: async (args, { abortSignal }) => {
    try {
      // Check signal for cancellation
      if (abortSignal?.aborted) {
        return { action: 'cancelled' };
      }

      const config = CanvasConfigSchema.parse(args);
      return {
        action: 'open_canvas' as const,
        canvasConfig: config,
      };
    } catch (error) {
      if (error instanceof z.ZodError) {
        return {
          action: 'error',
          error: 'Invalid canvas configuration',
          details: error.errors,
        };
      }
      throw error; // Re-throw unexpected errors
    }
  },
});

UI Error Display

tsx
function ToolErrorDisplay({ error }: { error: { message: string; details?: unknown } }) {
  return (
    <div className="rounded-lg border border-destructive/50 bg-destructive/10 p-3">
      <p className="text-sm text-destructive font-medium">{error.message}</p>
      {error.details && (
        <pre className="text-xs mt-2 text-muted-foreground">
          {JSON.stringify(error.details, null, 2)}
        </pre>
      )}
    </div>
  );
}

Model-Specific Considerations

Tuning for Different Models

typescript
// lib/ai/config/model-canvas.ts

export const CANVAS_MODEL_CONFIG = {
  'gemini-3-flash': {
    // Gemini tends to be less verbose
    toolCallBehavior: 'lenient',
    promptSuffix: 'When using tools, provide complete implementations.',
  },
  'grok-4.1-fast': {
    // Grok excels at agentic tool calling
    toolCallBehavior: 'strict',
    maxToolRoundtrips: 5,
  },
  'claude-4.5': {
    toolCallBehavior: 'balanced',
    maxToolRoundtrips: 3,
  },
} as const;

Testing Checklist

  • open_canvas creates correct configuration
  • update_canvas merges properly
  • run_code returns structured results
  • GenUI components render correctly
  • Tool errors display user-friendly messages
  • Artifact tracking persists data
  • Tool cancellation works
  • Model-specific tuning applied

Didn't find tool you were looking for?

Be as detailed as possible for better results