ai-query对接新版freesun-agent接口的分支
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

992 lines
35 KiB

/**
* 文件作用: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()}`;
};