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
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
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
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
// 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
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
// 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
// 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
// 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
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
// AI should return structured code blocks
interface CodeGenerationResult {
code: string;
explanation: string;
examples: string[];
nextSteps: string[];
}
Tool Invocation Tracking
Artifact Persistence
// 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
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
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
// 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
Weekly Installs
3
Repository
omerakben/omer-akbenGitHub Stars
1
First Seen
Feb 28, 2026
Security Audits
Installed on
opencode3
gemini-cli3
codebuddy3
github-copilot3
codex3
kimi-cli3