Files
ai-terminal-assistant/packages/server/src/routes/sessions.ts
T
kurihada 9f456c1029 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 读取
2025-12-15 13:35:32 +08:00

212 lines
4.9 KiB
TypeScript

/**
* Sessions API Routes
*
* 会话管理相关的 REST API
*/
import { Hono } from 'hono';
import { getSessionManager } from '../session/manager.js';
import { CreateSessionInputSchema, type ToolCallInfo, type MergedMessage } from '../types.js';
export const sessionsRouter = new Hono();
const sessionManager = getSessionManager();
/**
* GET /sessions - 列出所有会话
*/
sessionsRouter.get('/', (c) => {
const sessions = sessionManager.list();
return c.json({
success: true,
data: sessions,
});
});
/**
* POST /sessions - 创建新会话
*/
sessionsRouter.post('/', async (c) => {
try {
const body = await c.req.json();
const input = CreateSessionInputSchema.parse(body);
const session = await sessionManager.create(input);
return c.json(
{
success: true,
data: session,
},
201
);
} catch (error) {
return c.json(
{
success: false,
error: error instanceof Error ? error.message : 'Invalid input',
},
400
);
}
});
/**
* GET /sessions/:id - 获取单个会话
*/
sessionsRouter.get('/:id', (c) => {
const id = c.req.param('id');
const session = sessionManager.get(id);
if (!session) {
return c.json(
{
success: false,
error: 'Session not found',
},
404
);
}
return c.json({
success: true,
data: session,
});
});
/**
* DELETE /sessions/:id - 删除会话
*/
sessionsRouter.delete('/:id', async (c) => {
const id = c.req.param('id');
if (!sessionManager.exists(id)) {
return c.json(
{
success: false,
error: 'Session not found',
},
404
);
}
await sessionManager.delete(id);
return c.json({
success: true,
message: 'Session deleted',
});
});
/**
* GET /sessions/:id/messages - 获取会话消息
*
* 从 Core 存储读取消息,直接返回(存储层已经是 2-message 格式)
*
* 存储格式:
* - user 消息:TextPart(文本内容)
* - assistant 消息:TextPart(文本) + ToolPart(工具调用,含状态机)
*/
sessionsRouter.get('/:id/messages', async (c) => {
const id = c.req.param('id');
if (!sessionManager.exists(id)) {
return c.json(
{
success: false,
error: 'Session not found',
},
404
);
}
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<MessageInfo[]>;
};
PartStorage: {
getByIds(messageId: string, partIds: string[]): Promise<Part[]>;
};
};
// 获取消息列表(按创建时间排序)
const messageInfos = await MessageStorage.listBySession(id);
// 转换为前端格式
const messages: MergedMessage[] = [];
for (const msgInfo of messageInfos) {
const parts = await PartStorage.getByIds(msgInfo.id, msgInfo.partIds);
// 提取文本内容
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,
});
}
return c.json({
success: true,
data: messages,
});
} catch (error) {
console.error('[Sessions] Failed to load messages:', error);
return c.json({
success: true,
data: [],
});
}
});