import { z } from "zod"; import { getCanvasCapability, parseCanvasCapabilityArgs } from "./registry"; import { CANVAS_CAPABILITY_IDS, type CanvasCapabilityCommandSource, type CanvasCapabilityId, } from "./types"; export const canvasCapabilityClientCommandSchema = z.object({ version: z.literal(1), requestId: z.string().trim().min(1).max(180), capabilityId: z.enum(CANVAS_CAPABILITY_IDS), projectId: z.string().trim().min(1).max(180).optional(), nodeIds: z.array(z.string().trim().min(1).max(180)).min(1).max(50), args: z.record(z.unknown()).default({}), source: z.enum(["toolbar", "agent", "mcp", "system"]), confirmed: z.boolean().optional(), createdAt: z.string().datetime(), }); export type CanvasCapabilityClientCommand = z.infer< typeof canvasCapabilityClientCommandSchema >; export function createCanvasCapabilityClientCommand(params: { capabilityId: CanvasCapabilityId; nodeIds: string[]; projectId?: string; requestId?: string; args?: unknown; source: CanvasCapabilityCommandSource; confirmed?: boolean; createdAt?: string; }): CanvasCapabilityClientCommand { const capability = getCanvasCapability(params.capabilityId); if (!capability) throw new Error(`Unknown canvas capability: ${params.capabilityId}`); const nodeIds = Array.from(new Set(params.nodeIds.map((id) => id.trim()).filter(Boolean))); if (nodeIds.length < capability.selection.min) { throw new Error(`${capability.title} requires at least ${capability.selection.min} selected node(s)`); } if (capability.selection.max && nodeIds.length > capability.selection.max) { throw new Error(`${capability.title} supports at most ${capability.selection.max} selected node(s)`); } if (capability.confirmation === "always" && params.confirmed !== true) { throw new Error(`${capability.title} requires explicit confirmation`); } return canvasCapabilityClientCommandSchema.parse({ version: 1, requestId: params.requestId || crypto.randomUUID(), capabilityId: params.capabilityId, projectId: params.projectId, nodeIds, args: parseCanvasCapabilityArgs(params.capabilityId, params.args), source: params.source, confirmed: params.confirmed, createdAt: params.createdAt || new Date().toISOString(), }); }