/** * 文件作用:superAgent 会话数据工具 * 职责范围: * 1. 读取和维护本地任务会话标记 * 2. 归一化后端 session 与历史消息 * 3. 格式化左侧会话时间 * * 不负责: * - 调用后端接口 * - 修改 React 状态 * - 渲染会话列表 * * 维护说明: * - 当前任务合集由后端 session 加本地 tender session 标记共同识别 * - 任务侧栏始终展示固定「默认任务集」;后端第一条同名任务集会绑定到该槽位 */ "use strict"; import { normalizeCheckpoint } from "./checkpoint"; import { FOLDER_COLLAPSED_STORAGE_KEY, PENDING_EMPTY_SESSION_STORAGE_KEY, ACTIVE_SESSION_STORAGE_KEY, DEFAULT_TASK_FOLDER_ID, DEFAULT_TASK_FOLDER_NAME, TASK_SESSION_STORAGE_KEY, TASK_STORAGE_KEY, } from "../constants"; export const normalizeSessionTitle = (title, fallbackTitle) => { const sessionTitle = String(title || "").trim(); if (sessionTitle.toLowerCase() === "new session") return "新对话"; if (!sessionTitle) return fallbackTitle; return sessionTitle; }; export const readTaskListFromStorage = () => { try { const rawValue = window.localStorage.getItem(TASK_STORAGE_KEY); const parsedValue = JSON.parse(rawValue || "[]"); if (!Array.isArray(parsedValue)) return []; return parsedValue.map((item) => ({ ...item, title: normalizeSessionTitle(item?.title || item?.name || item?.summary, "新对话"), })); } catch (error) { return []; } }; export const saveTaskListToStorage = (taskList) => { window.localStorage.setItem(TASK_STORAGE_KEY, JSON.stringify(taskList)); }; export const readTenderSessionIds = () => { try { const rawValue = window.localStorage.getItem(TASK_SESSION_STORAGE_KEY); const parsedValue = JSON.parse(rawValue || "[]"); return Array.isArray(parsedValue) ? parsedValue.map((item) => String(item)).filter(Boolean) : []; } catch (error) { return []; } }; export const markTenderSessionId = (sessionId) => { if (!sessionId) return; const nextIds = Array.from(new Set([String(sessionId), ...readTenderSessionIds()])); window.localStorage.setItem(TASK_SESSION_STORAGE_KEY, JSON.stringify(nextIds)); }; export const removeTenderSessionId = (sessionId) => { if (!sessionId) return; const nextIds = readTenderSessionIds().filter((item) => item !== String(sessionId)); window.localStorage.setItem(TASK_SESSION_STORAGE_KEY, JSON.stringify(nextIds)); }; const normalizePendingEmptySessionMap = (parsedValue = {}) => { if (parsedValue?.sessionId && parsedValue?.type) { return { [String(parsedValue.type)]: { sessionId: String(parsedValue.sessionId), type: String(parsedValue.type), folderId: parsedValue.folderId || parsedValue.folder_id || null, }, }; } return ["conversation", "task"].reduce((sessionMap, type) => { const item = parsedValue?.[type]; if (item?.sessionId) { sessionMap[type] = { sessionId: String(item.sessionId), type, folderId: item.folderId || item.folder_id || null, }; } return sessionMap; }, {}); }; export const readPendingEmptySessionMap = () => { try { const rawValue = window.localStorage.getItem(PENDING_EMPTY_SESSION_STORAGE_KEY); const parsedValue = JSON.parse(rawValue || "{}"); return normalizePendingEmptySessionMap(parsedValue); } catch (error) { return {}; } }; export const readPendingEmptySession = (type = "") => { const sessionMap = readPendingEmptySessionMap(); if (type) return sessionMap[type] || null; return sessionMap.conversation || sessionMap.task || null; }; export const markPendingEmptySession = (sessionId, type, folderId = null) => { if (!sessionId || !type) return; const sessionMap = readPendingEmptySessionMap(); const nextSessionMap = { ...sessionMap, [type]: { sessionId: String(sessionId), type: String(type), folderId: folderId || null, }, }; window.localStorage.setItem( PENDING_EMPTY_SESSION_STORAGE_KEY, JSON.stringify(nextSessionMap) ); }; export const clearPendingEmptySession = (sessionId = "") => { if (!sessionId) { window.localStorage.removeItem(PENDING_EMPTY_SESSION_STORAGE_KEY); return; } const sessionMap = readPendingEmptySessionMap(); const nextSessionMap = Object.entries(sessionMap).reduce((result, [type, item]) => { if (item?.sessionId !== String(sessionId)) { result[type] = item; } return result; }, {}); if (Object.keys(nextSessionMap).length) { window.localStorage.setItem( PENDING_EMPTY_SESSION_STORAGE_KEY, JSON.stringify(nextSessionMap) ); return; } window.localStorage.removeItem(PENDING_EMPTY_SESSION_STORAGE_KEY); }; export const readActiveSessionId = () => { try { return String(window.localStorage.getItem(ACTIVE_SESSION_STORAGE_KEY) || "").trim(); } catch (error) { return ""; } }; export const saveActiveSessionId = (sessionId) => { if (!sessionId) return; window.localStorage.setItem(ACTIVE_SESSION_STORAGE_KEY, String(sessionId)); }; export const clearActiveSessionId = (sessionId = "") => { if (!sessionId) { window.localStorage.removeItem(ACTIVE_SESSION_STORAGE_KEY); return; } if (readActiveSessionId() === String(sessionId)) { window.localStorage.removeItem(ACTIVE_SESSION_STORAGE_KEY); } }; export const readCollapsedFolderMap = () => { try { const rawValue = window.localStorage.getItem(FOLDER_COLLAPSED_STORAGE_KEY); const parsedValue = JSON.parse(rawValue || "{}"); return parsedValue && typeof parsedValue === "object" ? parsedValue : {}; } catch (error) { return {}; } }; export const saveCollapsedFolderMap = (folderMap) => { window.localStorage.setItem(FOLDER_COLLAPSED_STORAGE_KEY, JSON.stringify(folderMap || {})); }; export const getSessionKind = (item = {}) => { const taskType = item.task_type || item.taskType; if (taskType === "conversation") return "conversation"; if (taskType) return "task"; const kind = item.session_kind || item.sessionKind || item.type; if (kind === "task" || kind === "conversation") return kind; return "conversation"; }; export const isTenderSession = (item = {}) => { const sessionId = item.session_id || item.id || item.chatId || item.chat_id || ""; if (!sessionId) return false; const kind = getSessionKind(item); if (kind === "conversation") return false; if (kind === "task" || item.isTask || item.scene === "tender") return true; return readTenderSessionIds().includes(String(sessionId)); }; export const normalizeSidebarSession = (item = {}, realm = "conversation") => { const conversationId = item.id || item.conversation_id || item.conversationId || ""; const threadId = item.thread_id || item.threadId || ""; const sessionKind = getSessionKind({ ...item, type: item.type || realm }); const isTask = sessionKind === "task"; const tasksetId = item.taskset_id || item.tasksetId || item.folder_id || item.folderId || null; return { id: conversationId, chatId: conversationId, conversationId, threadId, title: normalizeSessionTitle(item.title || item.name || item.summary, isTask ? "未命名任务" : "新对话"), time: item.updated_at || item.updateTime || item.updatedAt || item.created_at || item.createdAt || Date.now(), sessionKind, type: sessionKind, taskType: item.task_type || item.taskType || "", status: item.status || "", isTask, folderId: tasksetId, tasksetId, sortOrder: Number(item.sort_order ?? item.sortOrder ?? 0), hasActiveTask: Boolean(item.has_active_task ?? item.hasActiveTask), raw: item, }; }; export const buildSidebarTreeFromAgentResources = (tasksets = [], conversations = []) => { const sessions = (conversations || []).map((item) => normalizeSidebarSession(item, item.type)); const tasks = sessions.filter((session) => session.sessionKind === "task"); const chats = sessions.filter((session) => session.sessionKind !== "task"); const folders = (tasksets || []).map((taskset) => ({ id: taskset.id, name: taskset.name || "未命名任务集", sortOrder: 0, updatedAt: taskset.updated_at || taskset.updatedAt || "", realm: "task", isVirtualDefault: false, sessions: tasks.filter((session) => session.tasksetId === taskset.id), })); return { realms: [ { realm: "task", label: "任务合集", folders, sessions: [], }, { realm: "conversation", label: "普通对话", folders: [], sessions: chats, }, ], }; }; export const normalizeSidebarFolder = (folder = {}, realm = "conversation") => ({ id: folder.id || folder.folder_id || "", name: folder.name || "未命名文件夹", sortOrder: Number(folder.sort_order ?? folder.sortOrder ?? 0), updatedAt: folder.updated_at || folder.updatedAt || "", realm, isVirtualDefault: Boolean(folder.isVirtualDefault), sessions: (folder.sessions || []).map((session) => normalizeSidebarSession(session, realm)), }); export const isVirtualDefaultFolder = (folderOrId) => { if (!folderOrId) return false; if (typeof folderOrId === "string") { return folderOrId === DEFAULT_TASK_FOLDER_ID; } return Boolean( folderOrId.isVirtualDefault || folderOrId.id === DEFAULT_TASK_FOLDER_ID ); }; const isPlaceholderDefaultFolderId = (folderId) => !folderId || folderId === DEFAULT_TASK_FOLDER_ID; /** 占位默认任务集(__default__)→ null;已绑定真实任务集则返回真实 id */ export const resolveApiFolderId = (folderId) => { if (!folderId) return null; if (typeof folderId === "string") { return isPlaceholderDefaultFolderId(folderId) ? null : folderId; } if (isPlaceholderDefaultFolderId(folderId.id)) return null; return folderId.id; }; /** * 任务合集展示归一化:页面始终保留一个固定「默认任务集」。 * 后端第一条同名真实任务集会绑定到该固定槽位,避免空库新建后出现两个默认任务集。 */ export const normalizeTaskRealmForDisplay = (realmItem = {}) => { const rootSessions = Array.isArray(realmItem.sessions) ? realmItem.sessions : []; const incomingFolders = Array.isArray(realmItem.folders) ? realmItem.folders : []; const placeholderFolders = incomingFolders.filter((folder) => isPlaceholderDefaultFolderId(folder?.id) ); const realFolders = incomingFolders.filter((folder) => !isPlaceholderDefaultFolderId(folder?.id) ); const boundDefaultFolders = realFolders.filter((folder) => folder?.isVirtualDefault || String(folder.name || "").trim() === DEFAULT_TASK_FOLDER_NAME ); const otherFolders = realFolders.filter((folder) => !folder?.isVirtualDefault && String(folder.name || "").trim() !== DEFAULT_TASK_FOLDER_NAME ); const boundDefaultFolder = boundDefaultFolders[0] || null; const virtualDefaultFolder = { id: boundDefaultFolder?.id || DEFAULT_TASK_FOLDER_ID, name: DEFAULT_TASK_FOLDER_NAME, sortOrder: -1000, updatedAt: boundDefaultFolder?.updatedAt || "", realm: "task", isVirtualDefault: true, sessions: [ ...placeholderFolders.flatMap((folder) => folder.sessions || []), ...boundDefaultFolders.flatMap((folder) => folder.sessions || []), ...rootSessions, ], }; return { ...realmItem, realm: "task", folders: [virtualDefaultFolder, ...otherFolders], sessions: [], }; }; export const normalizeSidebarTree = (payload = {}) => { if (Array.isArray(payload)) { const sessions = payload.map((session) => normalizeSidebarSession(session)); return { realms: [ { realm: "task", label: "任务合集", folders: [], sessions: sessions.filter((session) => session.sessionKind === "task"), }, { realm: "conversation", label: "普通对话", folders: [], sessions: sessions.filter((session) => session.sessionKind === "conversation"), }, ], }; } const realms = Array.isArray(payload.realms) ? payload.realms : []; return { realms: realms.map((realmItem) => { const realm = realmItem.realm === "task" ? "task" : "conversation"; return { realm, label: realmItem.label || (realm === "task" ? "任务合集" : "普通对话"), folders: (realmItem.folders || []).map((folder) => normalizeSidebarFolder(folder, realm)), sessions: (realmItem.sessions || []).map((session) => normalizeSidebarSession(session, realm)), }; }), }; }; export const getRealmFromSidebarTree = (tree, realm) => (tree?.realms || []).find((item) => item.realm === realm) || null; export const flattenRealmSessions = (realmItem = {}) => { const rootSessions = realmItem.sessions || []; const folderSessions = (realmItem.folders || []).flatMap((folder) => folder.sessions || []); return [...folderSessions, ...rootSessions]; }; export const findSessionInSidebarTree = (tree, sessionId) => { if (!sessionId) return null; for (const realmItem of tree?.realms || []) { for (const session of realmItem.sessions || []) { if (session.id === sessionId) { return { session, realm: realmItem.realm, folderId: null }; } } for (const folder of realmItem.folders || []) { for (const session of folder.sessions || []) { if (session.id === sessionId) { return { session, realm: realmItem.realm, folderId: folder.id }; } } } } return null; }; export const updateSessionInSidebarTree = (tree, sessionId, patch = {}) => { if (!tree || !sessionId) return tree; const nextTree = { realms: (tree.realms || []).map((realmItem) => ({ ...realmItem, sessions: (realmItem.sessions || []).map((session) => session.id === sessionId ? { ...session, ...patch } : session ), folders: (realmItem.folders || []).map((folder) => ({ ...folder, sessions: (folder.sessions || []).map((session) => session.id === sessionId ? { ...session, ...patch } : session ), })), })), }; return nextTree; }; export const removeSessionFromSidebarTree = (tree, sessionId) => { if (!tree || !sessionId) return tree; return { realms: (tree.realms || []).map((realmItem) => ({ ...realmItem, sessions: (realmItem.sessions || []).filter((session) => session.id !== sessionId), folders: (realmItem.folders || []).map((folder) => ({ ...folder, sessions: (folder.sessions || []).filter((session) => session.id !== sessionId), })), })), }; }; export const normalizeSuperAgentSession = (item = {}) => { const sessionId = item.session_id || item.id || item.chatId || item.chat_id || ""; const sessionKind = getSessionKind(item); const isTask = sessionKind === "task"; const rawTitle = item.title || item.name || item.summary; return { id: sessionId, chatId: sessionId, title: normalizeSessionTitle(rawTitle, isTask ? "未命名任务" : "新对话"), time: item.updated_at || item.updateTime || item.updatedAt || item.created_at || item.createdAt || Date.now(), sessionKind, type: sessionKind, status: item.status || "", isTask, folderId: item.folder_id || item.folderId || null, sortOrder: Number(item.sort_order ?? item.sortOrder ?? 0), hasActiveTask: Boolean(item.has_active_task ?? item.hasActiveTask), raw: item, }; }; const getFileNameFromUrl = (url, fallbackName) => { const fileName = String(url || "").split("/").pop() || fallbackName || "已上传文件"; try { return decodeURIComponent(fileName); } catch (error) { return fileName; } }; const normalizeFileNameKey = (fileName = "") => { const rawFileName = String(fileName || "").trim(); if (!rawFileName) return ""; const cleanFileName = rawFileName.split("?")[0].split("#")[0]; try { return decodeURIComponent(cleanFileName).trim().toLowerCase(); } catch (error) { return cleanFileName.trim().toLowerCase(); } }; const getHistoryMessageContent = (item = {}, checkpoint = null) => { const contentCandidates = [ item.display_content, item.displayContent, item.data?.display_content, item.data?.displayContent, item.content, item.text, item.message, checkpoint?.description, ].filter((content) => content !== undefined && content !== null); const fileReferenceContent = contentCandidates.find((content) => /文件名|招标文件|文件路径|路径为/.test(String(content)) ); return fileReferenceContent ?? contentCandidates[0] ?? ""; }; const restoreHistoryFileReferences = (content, item = {}, messageIndex = 0) => { const rawFileList = [ item.files, item.file_list, item.attachments, item.data?.files, item.data?.file_list, item.data?.attachments, item.metadata?.files, item.metadata?.attachments, item.additional_kwargs?.files, item.additional_kwargs?.attachments, ].find(Array.isArray) || []; const fileList = Array.isArray(rawFileList) ? rawFileList.map((rawFile, index) => { const file = typeof rawFile === "string" ? { url: rawFile } : rawFile || {}; const url = file.url || file.path || file.file_path || file.filePath || file.download_url || file.downloadUrl || file.object_key || file.objectKey || ""; return { ...file, id: file.id || `history_file_${messageIndex}_${index + 1}`, attachmentId: file.attachmentId || file.attachment_id || file.id || "", name: file.name || file.filename || file.original_filename || file.originalFilename || file.display_name || file.displayName || getFileNameFromUrl(url), url, objectKey: file.object_key || file.objectKey || "", localPath: file.local_path || file.localPath || "", storage: file.storage || file.storage_provider || file.storageProvider || "", }; }) : []; const fileMap = new Map(fileList.filter((file) => file.url).map((file) => [file.url, file])); const fileNameSet = new Set( fileList.map((file) => normalizeFileNameKey(file.name)).filter(Boolean) ); const contentWithoutReferences = String(content || "").replace( /(\s*(?:文件名|招标文件)[::]\s*([\s\S]*?)[,,]\s*(?:文件路径|路径为)[::]\s*([\s\S]*?))/g, (referenceText, matchedFileName, url) => { const fileName = matchedFileName?.trim() || getFileNameFromUrl(url); const normalizedUrl = String(url || "").trim(); const fileNameKey = normalizeFileNameKey(fileName); if (!fileMap.has(normalizedUrl) && !fileNameSet.has(fileNameKey)) { fileMap.set(normalizedUrl, { id: `history_file_${messageIndex}_${fileMap.size + 1}`, name: fileName, url: normalizedUrl, }); if (fileNameKey) fileNameSet.add(fileNameKey); } return ""; } ); return { files: Array.from(fileMap.values()), content: contentWithoutReferences.replace(/^\s*\n?\s*|\s*\n?\s*$/g, ""), }; }; const isMergeableAssistantHistoryMessage = (item = {}) => { if (item.role !== "assistant") return false; const type = String(item.type || "plain"); return !["checkpoint", "taskProgress", "content"].includes(type); }; const mergeHistoryRunLog = (prevRunLog, nextRunLog) => { if (!prevRunLog) return nextRunLog || null; if (!nextRunLog) return prevRunLog; return { ...prevRunLog, ...nextRunLog, questionCount: Number(prevRunLog.questionCount || 0) + Number(nextRunLog.questionCount || 0), commandCount: Number(prevRunLog.commandCount || 0) + Number(nextRunLog.commandCount || 0), elapsedMs: Number(prevRunLog.elapsedMs || 0) + Number(nextRunLog.elapsedMs || 0), entries: [ ...(Array.isArray(prevRunLog.entries) ? prevRunLog.entries : []), ...(Array.isArray(nextRunLog.entries) ? nextRunLog.entries : []), ], }; }; const parseHistoryToolArguments = (toolCall = {}) => { const rawArguments = getSuperAgentToolArguments(toolCall); if (!rawArguments) return {}; if (typeof rawArguments === "object") return rawArguments; try { return JSON.parse(String(rawArguments)); } catch (error) { return {}; } }; const getHistoryCheckpointFromToolCall = (toolCall = {}) => { const toolName = getSuperAgentToolName(toolCall); if (toolName !== "emit_checkpoint") return null; const toolArguments = parseHistoryToolArguments(toolCall); return ( toolArguments.checkpoint || toolArguments.data?.checkpoint || toolArguments ); }; const hasHistoryCheckpointToolCall = (item = {}) => { const toolCalls = item.tool_calls || item.toolCalls || item.data?.tool_calls || []; if (!Array.isArray(toolCalls)) return false; return toolCalls.some((toolCall) => Boolean(getHistoryCheckpointFromToolCall(toolCall))); }; const buildHistoryCheckpointMessage = ({ checkpoint, sourceItem, sourceIndex, checkpointIndex, sessionId, }) => { const normalizedCheckpoint = normalizeCheckpoint(checkpoint); const checkpointId = normalizedCheckpoint.checkpoint_id; const checkpointMessageKey = checkpointId && sessionId ? `state_checkpoint_${sessionId}_${checkpointId}` : ""; const checkpointStatus = normalizedCheckpoint.checkpoint_status || sourceItem.checkpointStatus || "pending"; const isCollapsedAfterRespond = [ "confirmed", "completed", "done", "success", "regenerate", ].includes(String(checkpointStatus || "").toLowerCase()); return { key: checkpointMessageKey || `${sourceItem.id || sourceItem.message_id || `history_${sourceIndex}`}_checkpoint_${checkpointIndex}`, role: "assistant", type: "checkpoint", title: normalizedCheckpoint.title, content: normalizedCheckpoint.description, checkpoint: normalizedCheckpoint, task: sourceItem.task || sourceItem.data?.task || checkpoint?.task || null, checkpointStatus, isCollapsedAfterRespond, isRestoredFromHistory: true, }; }; const mergeAssistantHistoryMessages = (messageList = []) => { return messageList.reduce((result, item) => { const prevItem = result[result.length - 1]; if ( isMergeableAssistantHistoryMessage(prevItem) && isMergeableAssistantHistoryMessage(item) ) { const prevContent = String(prevItem.content || "").trim(); const nextContent = String(item.content || "").trim(); const mergedContent = [prevContent, nextContent].filter(Boolean).join("\n\n"); result[result.length - 1] = { ...prevItem, content: mergedContent, runLog: mergeHistoryRunLog(prevItem.runLog, item.runLog), loadingText: prevItem.loadingText || item.loadingText || "", }; return result; } result.push(item); return result; }, []); }; //[不计入运行日志的流程控制工具:对应内容已由 checkpoint 卡片单独展示] const RUN_LOG_IGNORED_TOOL_NAMES = new Set(["emit_checkpoint"]); //[读取工具调用名称,兼容历史消息和实时事件的不同字段] export const getSuperAgentToolName = (toolCall = {}) => { if (typeof toolCall === "string") return toolCall; return String( toolCall.tool || toolCall.tool_name || toolCall.name || toolCall.function?.name || toolCall.data?.tool || toolCall.data?.tool_name || toolCall.data?.name || "" ).trim(); }; //[读取工具调用标识,用于实时开始/结束事件合并] export const getSuperAgentToolCallId = (toolCall = {}) => String( toolCall.call_id || toolCall.callId || toolCall.tool_call_id || toolCall.toolCallId || toolCall.id || toolCall.data?.call_id || toolCall.data?.tool_call_id || toolCall.data?.id || "" ).trim(); //[读取工具参数,保留原始结构供展开区格式化] export const getSuperAgentToolArguments = (toolCall = {}) => toolCall.args || toolCall.arguments || toolCall.input || toolCall.parameters || toolCall.function?.arguments || toolCall.data?.args || toolCall.data?.arguments || toolCall.data?.input || ""; //[读取工具结果,兼容实时事件常见字段] export const getSuperAgentToolResult = (toolCall = {}) => toolCall.result || toolCall.output || toolCall.response || toolCall.data?.result || toolCall.data?.output || toolCall.data || ""; //[需要展示的运行工具:checkpoint 等流程控制事件不重复计数] export const isSuperAgentRunLogTool = (toolName = "") => { const normalizedName = String(toolName || "").trim(); return Boolean(normalizedName) && !RUN_LOG_IGNORED_TOOL_NAMES.has(normalizedName); }; //[询问类工具单独计数,其他工具统一归入命令执行] export const isSuperAgentQuestionTool = (toolName = "") => /(?:ask|question|request_user_input)/i.test(String(toolName || "")); //[构建历史会话运行摘要,避免切换会话后工具轨迹消失] export const buildSuperAgentRunLog = (toolCalls = [], sourceItem = {}) => { if (!Array.isArray(toolCalls) || !toolCalls.length) return null; let questionCount = 0; let commandCount = 0; const entries = []; toolCalls.forEach((toolCall, index) => { const toolName = getSuperAgentToolName(toolCall); if (!isSuperAgentRunLogTool(toolName)) return; const isQuestion = isSuperAgentQuestionTool(toolName); if (isQuestion) { questionCount += 1; } else { commandCount += 1; } entries.push({ id: getSuperAgentToolCallId(toolCall) || `history_tool_${index + 1}`, name: toolName, kind: isQuestion ? "question" : "command", status: "done", arguments: getSuperAgentToolArguments(toolCall), result: getSuperAgentToolResult(toolCall), }); }); if (!questionCount && !commandCount) return null; const elapsedMs = Number( sourceItem.elapsed_ms || sourceItem.elapsedMs || sourceItem.duration_ms || sourceItem.durationMs || 0 ); return { questionCount, commandCount, elapsedMs: Number.isFinite(elapsedMs) ? elapsedMs : 0, status: "done", entries, }; }; export const normalizeHistoryMessages = (messages = [], sessionId = "") => { //[历史 tool 结果按 call id 回填到对应调用,缺少结果时仍保留调用参数] const toolResultMap = new Map(); messages.forEach((item) => { const role = item?.role || item?.sender || item?.type || ""; if (role !== "tool") return; const callId = getSuperAgentToolCallId(item); const result = item.content || item.text || item.message || getSuperAgentToolResult(item); if (!callId || !result || String(result).trim() === "[工具执行完成]") return; toolResultMap.set(callId, result); }); const buildHistoryRunLog = (item = {}) => { const toolCalls = item.tool_calls || item.toolCalls || item.data?.tool_calls || []; const runLog = buildSuperAgentRunLog(toolCalls, item); if (!runLog) return null; return { ...runLog, entries: runLog.entries.map((entry) => ({ ...entry, result: entry.result || toolResultMap.get(entry.id) || "", })), }; }; const normalizedMessages = messages .filter((item) => { const role = item?.role || item?.sender || item?.type || ""; const checkpoint = item?.checkpoint || item?.data?.checkpoint || null; const content = getHistoryMessageContent(item, checkpoint); const isUser = role === "user" || role === "human"; const isAssistant = role === "assistant"; const isCheckpoint = Boolean(checkpoint); const hasDisplayContent = Boolean( String(content || "").trim() || checkpoint || buildHistoryRunLog(item) || hasHistoryCheckpointToolCall(item) ); return (isUser || isAssistant || isCheckpoint) && hasDisplayContent; }) .flatMap((item, index) => { const role = item.role || item.sender || item.type || ""; const isUser = role === "user" || role === "human"; const rawCheckpoint = item.checkpoint || item.data?.checkpoint || null; const checkpoint = rawCheckpoint ? normalizeCheckpoint(rawCheckpoint) : null; const task = item.task || item.data?.task || rawCheckpoint?.task || null; const rawContent = getHistoryMessageContent(item, checkpoint); const checkpointStatus = checkpoint ? checkpoint.checkpoint_status || item.checkpointStatus || "pending" : undefined; const checkpointId = checkpoint?.checkpoint_id || checkpoint?.checkpointId || checkpoint?.id || ""; const checkpointMessageKey = checkpoint && sessionId && checkpointId ? `state_checkpoint_${sessionId}_${checkpointId}` : ""; const historyFileData = isUser ? restoreHistoryFileReferences(rawContent, item, index) : { files: [], content: rawContent }; const isCollapsedAfterRespond = [ "confirmed", "completed", "done", "success", "regenerate", ].includes(String(checkpointStatus || "").toLowerCase()); const historyMessage = { key: checkpointMessageKey || item.id || item.message_id || `history_${index}`, role: isUser ? "user" : "assistant", type: checkpoint ? "checkpoint" : item.type || "plain", title: item.title || checkpoint?.title || "", content: checkpoint ? checkpoint.description || rawContent : historyFileData.content, reasoning: item.reasoning || item.thinking || "", runLog: buildHistoryRunLog(item), checkpoint, task, checkpointStatus, isCollapsedAfterRespond, isRestoredFromHistory: Boolean(checkpoint), fileName: item.fileName || item.file_name || "", files: historyFileData.files, }; if (isUser || checkpoint) return [historyMessage]; const toolCalls = item.tool_calls || item.toolCalls || item.data?.tool_calls || []; const checkpointMessages = Array.isArray(toolCalls) ? toolCalls .map((toolCall, checkpointIndex) => { const toolCheckpoint = getHistoryCheckpointFromToolCall(toolCall); if (!toolCheckpoint) return null; return buildHistoryCheckpointMessage({ checkpoint: toolCheckpoint, sourceItem: item, sourceIndex: index, checkpointIndex, sessionId, }); }) .filter(Boolean) : []; const hasHistoryContent = Boolean( String(historyMessage.content || "").trim() || historyMessage.runLog ); if (checkpointMessages.length && !hasHistoryContent) { return checkpointMessages; } return checkpointMessages.length ? [historyMessage, ...checkpointMessages] : [historyMessage]; }); return mergeAssistantHistoryMessages(normalizedMessages).reduce((messageList, item) => { const checkpointId = item.checkpoint?.checkpoint_id; const existingCheckpointIndex = checkpointId ? messageList.findIndex( (messageItem) => messageItem.checkpoint?.checkpoint_id === checkpointId ) : -1; if (existingCheckpointIndex >= 0) { const existingCheckpoint = messageList[existingCheckpointIndex]; messageList[existingCheckpointIndex] = { ...existingCheckpoint, ...item, key: existingCheckpoint.key, }; return messageList; } const previousItem = messageList[messageList.length - 1]; const isRunLogOnlyMessage = Boolean( item.role === "assistant" && item.type === "plain" && item.runLog && !String(item.title || "").trim() && !String(item.content || "").trim() && !item.checkpoint && !item.task ); const canMergeIntoPrevious = Boolean( isRunLogOnlyMessage && previousItem?.role === "assistant" && previousItem?.type === "plain" && previousItem?.runLog ); if (!canMergeIntoPrevious) { messageList.push(item); return messageList; } //[连续纯命令消息并入上一条回复,避免历史 tool_calls 形成多个独立日志块] const mergedEntries = [ ...(previousItem.runLog.entries || []), ...(item.runLog.entries || []), ].map((entry, index) => ({ ...entry, id: `${entry.id || entry.name || "tool"}_${index + 1}`, })); const mergedRunLog = { ...previousItem.runLog, status: previousItem.runLog.status === "running" || item.runLog.status === "running" ? "running" : "done", questionCount: mergedEntries.length ? mergedEntries.filter((entry) => entry.kind === "question").length : Number(previousItem.runLog.questionCount || 0) + Number(item.runLog.questionCount || 0), commandCount: mergedEntries.length ? mergedEntries.filter((entry) => entry.kind !== "question").length : Number(previousItem.runLog.commandCount || 0) + Number(item.runLog.commandCount || 0), elapsedMs: Number(previousItem.runLog.elapsedMs || 0) + Number(item.runLog.elapsedMs || 0), entries: mergedEntries, }; messageList[messageList.length - 1] = { ...previousItem, runLog: mergedRunLog, }; return messageList; }, []); }; export const formatChatTime = (rawTime) => { if (!rawTime) return ""; const timeValue = Number(rawTime); const dateValue = Number.isFinite(timeValue) ? new Date(timeValue < 10000000000 ? timeValue * 1000 : timeValue) : new Date(String(rawTime).replace(/-/g, "/")); if (!Number.isFinite(dateValue.getTime())) return ""; const today = new Date(); const isToday = dateValue.toDateString() === today.toDateString(); return isToday ? "今天" : `${dateValue.getMonth() + 1}-${dateValue.getDate()}`; };