import type { CanvasOperationPlan, CanvasViewOperationPlan, } from "@/types/canvas/agent-protocol"; export type PendingCanvasOperation = { version: 1; pendingId: string; projectId: string; operationId: string; operation: string; riskOperation: string; targetNodeIds: string[]; operationPlan?: CanvasOperationPlan; viewPlan?: CanvasViewOperationPlan; prompt: string; reply: string; requestedAt: string; requestedByMessageId: string; }; function trimString(value: unknown, maxLength: number) { return typeof value === "string" ? value.trim().slice(0, maxLength) : ""; } function normalizeStringArray(value: unknown, maxItems: number) { if (!Array.isArray(value)) return []; return value .map((item) => trimString(item, 160)) .filter(Boolean) .slice(0, maxItems); } function normalizeOperationPlan(value: unknown): CanvasOperationPlan | undefined { if (!value || typeof value !== "object" || Array.isArray(value)) return undefined; const raw = value as CanvasOperationPlan; if (raw.version !== 1 || !raw.operationId || !raw.operation || !raw.projectId) { return undefined; } return raw; } function normalizeViewPlan(value: unknown): CanvasViewOperationPlan | undefined { if (!value || typeof value !== "object" || Array.isArray(value)) return undefined; const raw = value as CanvasViewOperationPlan; if (raw.version !== 1 || !raw.viewOperationId || !raw.operation) { return undefined; } return raw; } export function extractPendingCanvasOperationFromWorkingState( workingState?: { lastTurnSummary?: unknown } | null ): PendingCanvasOperation | null { const summary = workingState?.lastTurnSummary; if (!summary || typeof summary !== "object" || Array.isArray(summary)) return null; const pending = (summary as Record).pendingCanvasOperation; if (!pending || typeof pending !== "object" || Array.isArray(pending)) return null; const record = pending as Record; const projectId = trimString(record.projectId, 160); const operationId = trimString(record.operationId, 180); const operation = trimString(record.operation, 80); const riskOperation = trimString(record.riskOperation, 80); const requestedByMessageId = trimString(record.requestedByMessageId, 160); const prompt = trimString(record.prompt, 1000); const reply = trimString(record.reply, 500); if (!projectId || !operationId || !operation || !riskOperation || !requestedByMessageId) { return null; } return { version: 1, pendingId: trimString(record.pendingId, 180) || operationId, projectId, operationId, operation, riskOperation, targetNodeIds: normalizeStringArray(record.targetNodeIds, 20), operationPlan: normalizeOperationPlan(record.operationPlan), viewPlan: normalizeViewPlan(record.viewPlan), prompt, reply, requestedAt: trimString(record.requestedAt, 80) || new Date(0).toISOString(), requestedByMessageId, }; } export function isCanvasOperationConfirmText(text: string) { const normalized = String(text || "").toLowerCase().replace(/\s+/g, ""); if (!normalized) return false; return /确认|同意|可以执行|执行吧|就这样|没问题|按这个|开始执行|确认执行/.test( normalized ); } export function isCanvasOperationCancelText(text: string) { const normalized = String(text || "").toLowerCase().replace(/\s+/g, ""); if (!normalized) return false; return /取消|先不|不要|别执行|不执行|算了|停止/.test(normalized); } export function buildPendingCanvasOperation(params: { projectId: string; messageId: string; userText: string; riskOperation: string; targetNodeIds: string[]; reply: string; operationPlan?: CanvasOperationPlan; viewPlan?: CanvasViewOperationPlan; }): PendingCanvasOperation { const operationId = params.operationPlan?.operationId || `${params.messageId}:pending-canvas-operation`; return { version: 1, pendingId: operationId, projectId: params.projectId, operationId, operation: params.operationPlan?.operation || params.riskOperation, riskOperation: params.riskOperation, targetNodeIds: params.targetNodeIds.slice(0, 20), operationPlan: params.operationPlan, viewPlan: params.viewPlan, prompt: params.userText.slice(0, 1000), reply: params.reply.slice(0, 500), requestedAt: new Date().toISOString(), requestedByMessageId: params.messageId, }; } export function withConfirmedCanvasOperationPlan( pending: PendingCanvasOperation, confirmingMessageId: string ): CanvasOperationPlan | null { if (!pending.operationPlan) return null; return { ...pending.operationPlan, confirmed: true, metadata: { ...(pending.operationPlan.metadata || {}), confirmedByUser: true, confirmedByMessageId: confirmingMessageId, confirmedAt: new Date().toISOString(), pendingOperationId: pending.pendingId, }, }; }