/** * Sessions API Routes * * 会话管理相关的 REST API */ import { Hono } from 'hono'; import { getSessionManager } from '../session/manager.js'; import { CreateSessionInputSchema, type ToolCallInfo, type Message, type MessagePart, } from '../types.js'; import type { MessageInfo, Part, ToolPart, ApiPart } from '@ai-assistant/core'; import { MessageStorage, PartStorage, partsToApiFormat, getToolInput, getToolDuration } from '@ai-assistant/core'; 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', }); }); /** * POST /sessions/:id/clear - 清空会话消息 * * 删除会话的所有消息和相关数据,但保留会话本身 */ sessionsRouter.post('/:id/clear', async (c) => { const id = c.req.param('id'); if (!sessionManager.exists(id)) { return c.json( { success: false, error: 'Session not found', }, 404 ); } try { // 删除会话的所有消息 await MessageStorage.removeBySession(id); // 重置会话状态 sessionManager.updateStatus(id, 'idle'); return c.json({ success: true, message: 'Session messages cleared', }); } catch (error) { console.error('[Sessions] Failed to clear messages:', error); return c.json( { success: false, error: error instanceof Error ? error.message : 'Failed to clear messages', }, 500 ); } }); /** * 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 { // 获取消息列表(按创建时间排序) const messageInfos = await MessageStorage.listBySession(id); // 转换为前端格式 const messages: Message[] = []; for (const msgInfo of messageInfos) { const parts = await PartStorage.getByIds(msgInfo.id, msgInfo.partIds); // 使用 Core 的转换函数将 Parts 转换为 API 格式 const apiParts = partsToApiFormat(parts); // 类型兼容:ApiPart 与 MessagePart 结构相同 const messageParts = apiParts as MessagePart[]; // 兼容字段:提取文本内容 const textContent = parts .filter((p) => p.type === 'text') .map((p) => p.text ?? '') .join(''); // 兼容字段:提取工具调用(使用 Core 工具函数) const toolCalls: ToolCallInfo[] = parts .filter((p): p is ToolPart => p.type === 'tool') .map((p) => ({ id: p.toolCallId ?? '', name: p.toolName ?? '', arguments: getToolInput(p), status: p.state.status, result: p.state.status === 'completed' ? (p.state as { output: unknown }).output : undefined, error: p.state.status === 'error' ? (p.state as { error: string }).error : undefined, duration: getToolDuration(p), })); messages.push({ id: msgInfo.id, sessionId: msgInfo.sessionId, role: msgInfo.role, timestamp: new Date(msgInfo.createdAt).toISOString(), parts: messageParts, content: textContent || undefined, 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: [], }); } });