Files
ai-terminal-assistant/packages/server/src/routes/sessions.ts
T
kurihada e0444a966f feat: 添加系统命令支持 (:clear)
- 新增系统命令模块 (core/system-commands)
  - 支持 :clear/:cls/:c 清空对话历史
  - 命令注册表支持别名
  - 可扩展的命令执行器

- Server 端支持
  - 新增 /api/system-commands API
  - WebSocket 处理系统命令消息
  - 会话清空 API 端点

- UI 端支持
  - 新增 SystemCommandMenu 组件
  - 输入 : 时显示命令建议菜单
  - 键盘导航和选择
  - 底部提示添加 : 快捷键
2025-12-17 19:25:42 +08:00

226 lines
5.2 KiB
TypeScript

/**
* 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: [],
});
}
});