refactor(storage): 重构消息存储为 2-message 格式
采用 OpenCode 风格的消息存储架构: - 只有 user 和 assistant 两种角色,移除 tool/system - ToolPart 使用状态机模式 (pending → running → completed/error) - 新增 toModelMessages() 转换函数用于调用 AI SDK - 删除 message-merger.ts,存储层直接返回正确格式 主要改动: - parts.ts: ToolState 状态机(pending/running/completed/error) - message.ts: 移除 system role,添加 parentId 关联 - converter.ts: 新增 toModelMessages() 格式转换 - manager.ts: 重构 syncMessages/partsToModelMessages - sessions.ts: 简化路由,直接从 Core Storage 读取
This commit is contained in:
@@ -6,8 +6,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';
|
||||
import { CreateSessionInputSchema, type ToolCallInfo, type MergedMessage } from '../types.js';
|
||||
|
||||
export const sessionsRouter = new Hono();
|
||||
|
||||
@@ -101,14 +100,11 @@ sessionsRouter.delete('/:id', async (c) => {
|
||||
/**
|
||||
* GET /sessions/:id/messages - 获取会话消息
|
||||
*
|
||||
* 从 Core 存储读取消息,合并为用户视角的对话轮次
|
||||
* 从 Core 存储读取消息,直接返回(存储层已经是 2-message 格式)
|
||||
*
|
||||
* 合并规则:
|
||||
* - 用户/系统消息:直接返回
|
||||
* - 助手消息:将连续的 assistant + tool 消息合并为一条
|
||||
* - content: 所有文本内容合并
|
||||
* - toolCalls: 工具调用列表(含参数、状态、结果)
|
||||
* - reasoning: 推理内容(如果有)
|
||||
* 存储格式:
|
||||
* - user 消息:TextPart(文本内容)
|
||||
* - assistant 消息:TextPart(文本) + ToolPart(工具调用,含状态机)
|
||||
*/
|
||||
sessionsRouter.get('/:id/messages', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
@@ -126,42 +122,84 @@ sessionsRouter.get('/:id/messages', async (c) => {
|
||||
try {
|
||||
// 动态导入 Core 存储 API
|
||||
const corePath = '@ai-assistant/core';
|
||||
type MessageInfo = {
|
||||
id: string;
|
||||
sessionId: string;
|
||||
role: 'user' | 'assistant';
|
||||
parentId?: string;
|
||||
createdAt: number;
|
||||
partIds: string[];
|
||||
};
|
||||
type Part = {
|
||||
id: string;
|
||||
createdAt: number;
|
||||
type: string;
|
||||
text?: string;
|
||||
toolCallId?: string;
|
||||
toolName?: string;
|
||||
state?: {
|
||||
status: 'pending' | 'running' | 'completed' | 'error';
|
||||
input?: Record<string, unknown>;
|
||||
output?: unknown;
|
||||
error?: string;
|
||||
time?: { start: number; end?: number };
|
||||
};
|
||||
};
|
||||
|
||||
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[] }>
|
||||
>;
|
||||
listBySession(sessionId: string): Promise<MessageInfo[]>;
|
||||
};
|
||||
PartStorage: {
|
||||
getByIds(messageId: string, partIds: string[]): Promise<RawPart[]>;
|
||||
getByIds(messageId: string, partIds: string[]): Promise<Part[]>;
|
||||
};
|
||||
};
|
||||
|
||||
// 获取消息列表(按创建时间排序)
|
||||
const messageInfos = await MessageStorage.listBySession(id);
|
||||
|
||||
// 获取每个消息的 Parts
|
||||
const messagesWithParts: MessageWithParts[] = [];
|
||||
// 转换为前端格式
|
||||
const messages: MergedMessage[] = [];
|
||||
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 textContent = parts
|
||||
.filter((p) => p.type === 'text')
|
||||
.map((p) => p.text ?? '')
|
||||
.join('');
|
||||
|
||||
// 提取工具调用
|
||||
const toolCalls: ToolCallInfo[] = parts
|
||||
.filter((p) => p.type === 'tool' && p.state)
|
||||
.map((p) => {
|
||||
const state = p.state!;
|
||||
const startTime = state.time?.start;
|
||||
const endTime = state.time?.end;
|
||||
return {
|
||||
id: p.toolCallId ?? '',
|
||||
name: p.toolName ?? '',
|
||||
arguments: state.input ?? {},
|
||||
status: state.status,
|
||||
result: state.status === 'completed' ? state.output : undefined,
|
||||
error: state.status === 'error' ? state.error : undefined,
|
||||
duration: startTime && endTime ? endTime - startTime : undefined,
|
||||
};
|
||||
});
|
||||
|
||||
messages.push({
|
||||
id: msgInfo.id,
|
||||
sessionId: msgInfo.sessionId,
|
||||
role: msgInfo.role,
|
||||
content: textContent,
|
||||
timestamp: new Date(msgInfo.createdAt).toISOString(),
|
||||
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// 合并消息
|
||||
const mergedMessages = mergeMessages(messagesWithParts);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: mergedMessages,
|
||||
data: messages,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Sessions] Failed to load messages:', error);
|
||||
|
||||
Reference in New Issue
Block a user