import type { CanvasOperationPlan } from "@/types/canvas/agent-protocol"; export type CanvasAgentOperationAuditCanvasData = { metadata?: unknown; [key: string]: unknown; }; function normalizeCanvasMetadata(value: unknown) { return value && typeof value === "object" && !Array.isArray(value) ? { ...(value as Record) } : {}; } export function getCanvasAgentOperationAudit( canvasData: CanvasAgentOperationAuditCanvasData ) { const metadata = normalizeCanvasMetadata(canvasData.metadata); const audit = Array.isArray(metadata.canvasAgentOperationAudit) ? metadata.canvasAgentOperationAudit : []; return audit.filter((item) => item && typeof item === "object").slice(-30); } export function findCanvasAgentOperationAudit( canvasData: CanvasAgentOperationAuditCanvasData, operationId: string ) { return getCanvasAgentOperationAudit(canvasData).find( (item) => String((item as Record).operationId || "").trim() === operationId ) as Record | undefined; } export function normalizeCanvasAgentAuditStringList(value: unknown) { return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : []; } export function buildIdempotentCanvasAgentAuditResult(params: { canvasData: CanvasAgentOperationAuditCanvasData; audit: Record; }) { return { canvasData: params.canvasData, createdNodeIds: normalizeCanvasAgentAuditStringList(params.audit.createdNodeIds), createdNodeIdsByMediaItemId: params.audit.createdNodeIdsByMediaItemId && typeof params.audit.createdNodeIdsByMediaItemId === "object" && !Array.isArray(params.audit.createdNodeIdsByMediaItemId) ? Object.fromEntries( Object.entries( params.audit.createdNodeIdsByMediaItemId as Record ) .map(([mediaItemId, nodeId]) => [ String(mediaItemId || "").trim(), String(nodeId || "").trim(), ]) .filter(([mediaItemId, nodeId]) => mediaItemId && nodeId) ) : {}, updatedNodeIds: normalizeCanvasAgentAuditStringList(params.audit.updatedNodeIds), createdEdgeIds: normalizeCanvasAgentAuditStringList(params.audit.createdEdgeIds), }; } export function appendCanvasAgentOperationAudit( params: { canvasData: T; auditEntry: Record; } ): T { const metadata = normalizeCanvasMetadata(params.canvasData.metadata); const audit = getCanvasAgentOperationAudit(params.canvasData); return { ...params.canvasData, metadata: { ...metadata, canvasAgentOperationAudit: [...audit, params.auditEntry].slice(-30), }, }; } export function appendCanvasAgentOperationAuditIfMissing< T extends CanvasAgentOperationAuditCanvasData, >(params: { canvasData: T; plan: CanvasOperationPlan; result: { createdNodeIds: string[]; createdNodeIdsByMediaItemId: Record; updatedNodeIds: string[]; createdEdgeIds: string[]; }; }) { if (findCanvasAgentOperationAudit(params.canvasData, params.plan.operationId)) { return params.canvasData; } return appendCanvasAgentOperationAudit({ canvasData: params.canvasData, auditEntry: { operationId: params.plan.operationId, operation: params.plan.operation, createdNodeIds: params.result.createdNodeIds, createdNodeIdsByMediaItemId: params.result.createdNodeIdsByMediaItemId, updatedNodeIds: params.result.updatedNodeIds, createdEdgeIds: params.result.createdEdgeIds, sourceNodeIds: params.plan.sourceNodeIds || [], targetNodeIds: params.plan.targetNodeIds || [], targetSlotId: params.plan.targetSlotId, anchorNodeId: params.plan.anchorNodeId, appliedAt: new Date().toISOString(), metadata: params.plan.metadata || {}, }, }); }