// 画布Agent对话契约:会话元数据存 SpacetimeDB,消息正文整体存 OSS(editor-agent/{conversationId}.json)。 export const EDITOR_AGENT_MAX_ATTACHMENTS = 9; export const EDITOR_AGENT_ATTACHMENT_LABEL_MAX_CODE_POINTS = 24; export const EDITOR_AGENT_ERROR_MESSAGE_PREFIX = 'ERROR '; export type EditorAgentMessageRole = 'user' | 'assistant' | 'system'; export type EditorAgentToolCallStatus = | 'not_completed' | 'completed' | 'failed' | 'cancelled'; export type EditorAgentAttachmentSource = 'canvas_resource' | 'library_asset'; function isUnsafeEditorAgentAttachmentLabelCharacter(character: string) { const codePoint = character.codePointAt(0) ?? 0; const isControlCharacter = codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f); const isAsciiPunctuation = (codePoint >= 0x21 && codePoint <= 0x2f) || (codePoint >= 0x3a && codePoint <= 0x40) || (codePoint >= 0x5b && codePoint <= 0x60) || (codePoint >= 0x7b && codePoint <= 0x7e); const isUnsafeAsciiPunctuation = isAsciiPunctuation && character !== '-' && character !== '_' && character !== '.'; return isControlCharacter || isUnsafeAsciiPunctuation; } function normalizeEditorAgentAttachmentLabel( label: string | null | undefined, fallback: string, ) { const normalizeCandidate = (candidate: string | null | undefined) => { const normalized: string[] = []; let codePoints = 0; let pendingSpace = false; for (const character of candidate?.trim() ?? '') { if (isUnsafeEditorAgentAttachmentLabelCharacter(character)) { continue; } if (/\s/u.test(character)) { pendingSpace = normalized.length > 0; continue; } if ( pendingSpace && codePoints + 1 < EDITOR_AGENT_ATTACHMENT_LABEL_MAX_CODE_POINTS ) { normalized.push(' '); codePoints += 1; } pendingSpace = false; if (codePoints >= EDITOR_AGENT_ATTACHMENT_LABEL_MAX_CODE_POINTS) { break; } normalized.push(character); codePoints += 1; } return normalized.join('').trim(); }; return normalizeCandidate(label) || normalizeCandidate(fallback) || '图片'; } export interface EditorAgentAttachmentRef { source: EditorAgentAttachmentSource; referenceId: string; objectKey?: string | null; imageSrc: string; thumbnailSrc?: string | null; label?: string | null; width?: number | null; height?: number | null; } export function createEditorAgentAttachmentRef( input: EditorAgentAttachmentRef, fallbackLabel: string = input.referenceId, ) { return { ...input, label: normalizeEditorAgentAttachmentLabel(input.label, fallbackLabel), }; } export interface EditorAgentGeneratedImage { resourceId?: string | null; objectKey?: string | null; assetObjectId?: string | null; imageSrc: string; thumbnailSrc?: string | null; width?: number | null; height?: number | null; } export interface EditorAgentGeneratedVideo { resourceId?: string | null; objectKey?: string | null; assetObjectId?: string | null; videoSrc: string; thumbnailSrc?: string | null; width?: number | null; height?: number | null; } export interface EditorAgentGeneratedAudio { resourceId?: string | null; objectKey?: string | null; assetObjectId?: string | null; audioSrc: string; } export interface EditorAgentToolCallStringArg { name: string; label: string; value: string; } export interface EditorAgentToolCallImageRef { imageId: string; imageSrc: string; objectKey?: string | null; thumbnailSrc?: string | null; label?: string | null; width?: number | null; height?: number | null; } export interface EditorAgentToolCallImageArg { name: string; label: string; refs: EditorAgentToolCallImageRef[]; } export interface EditorAgentToolCallDisplayExtras { priceMudPoints: number; } export interface EditorAgentToolCallDisplayArgs { stringArgs: EditorAgentToolCallStringArg[]; imageArgs: EditorAgentToolCallImageArg[]; extras: EditorAgentToolCallDisplayExtras; } export interface EditorAgentToolCall { toolName: string; status: EditorAgentToolCallStatus; args: unknown; displayArgs: EditorAgentToolCallDisplayArgs; externalJobId?: string | null; images: EditorAgentGeneratedImage[]; // Older persisted conversation documents do not contain these media fields. videos?: EditorAgentGeneratedVideo[]; audios?: EditorAgentGeneratedAudio[]; error?: string | null; } export interface EditorAgentMessage { // Frontend must not use this to organize messages. // It is an opaque backend locator for pending tool-call operations. id: number; // Present only on user messages created from a client send request. clientMessageId?: string | null; role: EditorAgentMessageRole; text: string; attachments: EditorAgentAttachmentRef[]; toolCall: EditorAgentToolCall | null; createdAt: string; } export interface EditorAgentConversationSummary { conversationId: string; projectId: string; title: string; updatedAt: string; } export interface EditorAgentConversationDetail { conversationId: string; projectId: string; title: string; createdAt: string; updatedAt: string; messages: EditorAgentMessage[]; } export interface EditorAgentConversationMessagesDocument { version: number; conversationId: string; messages: EditorAgentMessage[]; } export interface CreateEditorAgentConversationRequest { title?: string | null; } export interface EditorAgentConversationListResponse { conversations: EditorAgentConversationSummary[]; } export interface EditorAgentConversationResponse { conversation: EditorAgentConversationDetail; } export interface EditorAgentMessageRequest { clientMessageId: string; text: string; attachments?: EditorAgentAttachmentRef[]; } export interface EditorAgentMessageResponse { conversation: EditorAgentConversationSummary; deltaMessages: EditorAgentMessage[]; errorMessage: string | null; }