feat(api): 实现消息合并 API,支持工具调用显示

- 新增 MergedMessage、ToolCallInfo 类型定义
- 创建 message-merger.ts 消息合并工具
- 更新 sessions 路由使用合并后的消息格式
- 前端新增 ToolCallsDisplay 组件展示工具调用
- 工具调用显示状态、时长,可展开查看参数和结果
This commit is contained in:
2025-12-15 12:21:16 +08:00
parent e9e8bfa30a
commit eda2ccb171
7 changed files with 567 additions and 43 deletions
+50 -35
View File
@@ -7,6 +7,7 @@
import { Hono } from 'hono';
import { getSessionManager } from '../session/manager.js';
import { CreateSessionInputSchema } from '../types.js';
import { mergeMessages, type MessageWithParts, type RawPart } from '../utils/message-merger.js';
export const sessionsRouter = new Hono();
@@ -100,7 +101,14 @@ sessionsRouter.delete('/:id', async (c) => {
/**
* GET /sessions/:id/messages - 获取会话消息
*
* 从 Core 存储读取完整的消息历史(包含 tool-call 和 tool-result
* 从 Core 存储读取消息,合并为用户视角的对话轮次
*
* 合并规则:
* - 用户/系统消息:直接返回
* - 助手消息:将连续的 assistant + tool 消息合并为一条
* - content: 所有文本内容合并
* - toolCalls: 工具调用列表(含参数、状态、结果)
* - reasoning: 推理内容(如果有)
*/
sessionsRouter.get('/:id/messages', async (c) => {
const id = c.req.param('id');
@@ -115,44 +123,51 @@ sessionsRouter.get('/:id/messages', async (c) => {
);
}
// 从 Core 存储读取消息
const sessionData = await sessionManager.loadSessionData(id);
try {
// 动态导入 Core 存储 API
const corePath = '@ai-assistant/core';
const { MessageStorage, PartStorage } = (await import(/* webpackIgnore: true */ corePath)) as {
MessageStorage: {
listBySession(sessionId: string): Promise<
Array<{ id: string; sessionId: string; role: string; createdAt: number; partIds: string[] }>
>;
};
PartStorage: {
getByIds(messageId: string, partIds: string[]): Promise<RawPart[]>;
};
};
if (!sessionData) {
// 获取消息列表(按创建时间排序)
const messageInfos = await MessageStorage.listBySession(id);
// 获取每个消息的 Parts
const messagesWithParts: MessageWithParts[] = [];
for (const msgInfo of messageInfos) {
const parts = await PartStorage.getByIds(msgInfo.id, msgInfo.partIds);
messagesWithParts.push({
info: {
id: msgInfo.id,
sessionId: msgInfo.sessionId,
role: msgInfo.role as 'user' | 'assistant' | 'system' | 'tool',
createdAt: msgInfo.createdAt,
partIds: msgInfo.partIds,
},
parts,
});
}
// 合并消息
const mergedMessages = mergeMessages(messagesWithParts);
return c.json({
success: true,
data: mergedMessages,
});
} catch (error) {
console.error('[Sessions] Failed to load messages:', error);
return c.json({
success: true,
data: [],
});
}
// 为消息添加 ID 并转换内容格式(AI SDK 格式 -> 字符串)
const messagesWithId = sessionData.messages.map(
(msg: { role: string; content: unknown }, index: number) => {
// 转换 AI SDK 内容格式为字符串
let content: string;
if (typeof msg.content === 'string') {
content = msg.content;
} else if (Array.isArray(msg.content)) {
// AI SDK 格式: [{type: "text", text: "..."}, ...]
content = msg.content
.filter((block: { type?: string }) => block.type === 'text')
.map((block: { text?: string }) => block.text || '')
.join('');
} else {
content = String(msg.content);
}
return {
id: `${msg.role}-${id}-${index}`,
role: msg.role,
content,
timestamp: new Date().toISOString(),
};
}
);
return c.json({
success: true,
data: messagesWithId,
});
});