refactor(storage): 统一消息存储到 Core 层

问题:Server 端只存储最终文本响应,工具调用的中间消息丢失。

解决方案:
- Agent.chat() 返回 ChatResult,包含完整消息链
- Server SessionManager 简化为只管理会话元数据
- 消息 API 改为从 Core Storage 读取
- 移除 Server 端的消息存储和 addMessage 方法

影响范围:
- core: Agent.chat() 返回类型变更
- server: SessionManager 接口变更,移除消息存储
- server: GET /sessions/:id/messages 从 Core 读取
- server: 移除 POST /sessions/:id/messages 端点
This commit is contained in:
2025-12-15 10:04:22 +08:00
parent a657af9bb7
commit 6342a46e59
14 changed files with 273 additions and 503 deletions
@@ -5,6 +5,8 @@
*
* 注意:SessionManager 使用动态 import 加载 @ai-assistant/core。
* 测试中会创建新实例并记录初始会话数量,以确保测试独立性。
*
* 消息存储已移至 Core 层,Server 的 SessionManager 只负责会话元数据管理。
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
@@ -172,16 +174,6 @@ describe('SessionManager', () => {
expect(result).toBe(false);
});
it('删除会话时同时删除消息', async () => {
// 注意:这个测试会自己删除 session,不需要追踪
const session = await manager.create({ name: 'With Messages' });
await manager.addMessage(session.id, { role: 'user', content: 'Hello' });
await manager.delete(session.id);
expect(manager.getMessages(session.id)).toEqual([]);
});
it('删除后 count 减少', async () => {
// 注意:这个测试会自己删除 session,不需要追踪
const session = await manager.create({ name: 'Test' });
@@ -232,112 +224,28 @@ describe('SessionManager', () => {
});
});
describe('getMessages - 获取消息', () => {
it('返回会话消息', async () => {
const session = await createTrackedSession({ name: 'Test' });
await manager.addMessage(session.id, { role: 'user', content: 'Hello' });
await manager.addMessage(session.id, { role: 'assistant', content: 'Hi!' });
const messages = manager.getMessages(session.id);
expect(messages.length).toBe(2);
expect(messages[0].content).toBe('Hello');
expect(messages[1].content).toBe('Hi!');
});
it('新会话消息为空', async () => {
const session = await createTrackedSession({ name: 'Test' });
const messages = manager.getMessages(session.id);
expect(messages).toEqual([]);
});
it('不存在的会话返回空数组', () => {
const messages = manager.getMessages('non-existent-id-12345');
expect(messages).toEqual([]);
});
});
describe('addMessage - 添加消息', () => {
it('添加用户消息', async () => {
const session = await createTrackedSession({ name: 'Test' });
const message = await manager.addMessage(session.id, {
role: 'user',
content: 'Hello',
});
expect(message).toBeDefined();
expect(message?.role).toBe('user');
expect(message?.content).toBe('Hello');
expect(message?.id).toBeDefined();
expect(message?.sessionId).toBe(session.id);
});
it('添加助手消息', async () => {
const session = await createTrackedSession({ name: 'Test' });
const message = await manager.addMessage(session.id, {
role: 'assistant',
content: 'Hello!',
});
expect(message?.role).toBe('assistant');
});
it('添加系统消息', async () => {
const session = await createTrackedSession({ name: 'Test' });
const message = await manager.addMessage(session.id, {
role: 'system',
content: 'System prompt',
});
expect(message?.role).toBe('system');
});
it('消息有唯一 ID', async () => {
const session = await createTrackedSession({ name: 'Test' });
const msg1 = await manager.addMessage(session.id, { role: 'user', content: 'Hello' });
const msg2 = await manager.addMessage(session.id, { role: 'assistant', content: 'Hi!' });
expect(msg1?.id).not.toBe(msg2?.id);
});
it('消息有正确的时间戳', async () => {
const session = await createTrackedSession({ name: 'Test' });
const before = new Date().toISOString();
const message = await manager.addMessage(session.id, { role: 'user', content: 'Hello' });
const after = new Date().toISOString();
expect(message?.createdAt).toBeDefined();
expect(message?.createdAt! >= before).toBe(true);
expect(message?.createdAt! <= after).toBe(true);
});
it('更新会话的 messageCount', async () => {
describe('updateMessageCount - 更新消息计数', () => {
it('更新消息计数', async () => {
const session = await createTrackedSession({ name: 'Test' });
expect(session.messageCount).toBe(0);
await manager.addMessage(session.id, { role: 'user', content: 'Hello' });
const updated = manager.get(session.id);
expect(updated?.messageCount).toBe(1);
const updated = manager.updateMessageCount(session.id, 5);
expect(updated?.messageCount).toBe(5);
});
it('更新会话的 updatedAt', async () => {
it('更新时更新 updatedAt', async () => {
const session = await createTrackedSession({ name: 'Test' });
const originalUpdatedAt = session.updatedAt;
await new Promise((resolve) => setTimeout(resolve, 10));
await manager.addMessage(session.id, { role: 'user', content: 'Hello' });
manager.updateMessageCount(session.id, 3);
const updated = manager.get(session.id);
expect(updated?.updatedAt).not.toBe(originalUpdatedAt);
});
it('不存在的会话返回 undefined', async () => {
const result = await manager.addMessage('non-existent-id-12345', {
role: 'user',
content: 'Hello',
});
it('不存在的会话返回 undefined', () => {
const result = manager.updateMessageCount('non-existent-id-12345', 10);
expect(result).toBeUndefined();
});
});
@@ -410,48 +318,30 @@ describe('SessionManager', () => {
});
});
describe('getStorage 和 getProjectId - Storage 访问', () => {
it('getStorage 返回 Storage 实例或 null', () => {
const storage = manager.getStorage();
// 可能为 null(如果 Core 未加载)或者是 SessionStorage 实例
expect(storage === null || typeof storage === 'object').toBe(true);
});
it('getProjectId 返回字符串', async () => {
const session = await createTrackedSession({ name: 'Test' });
const projectId = manager.getProjectId(session.id);
expect(typeof projectId).toBe('string');
});
it('不存在的会话返回默认 projectId', () => {
const projectId = manager.getProjectId('non-existent-id');
expect(typeof projectId).toBe('string');
});
});
describe('边界情况', () => {
it('处理特殊字符的会话名称', async () => {
const session = await createTrackedSession({ name: '测试会话 <>&"\'`' });
expect(session.name).toBe('测试会话 <>&"\'`');
});
it('处理长消息内容', async () => {
const session = await createTrackedSession({ name: 'Test' });
const longContent = 'x'.repeat(10000);
const message = await manager.addMessage(session.id, {
role: 'user',
content: longContent,
});
expect(message?.content).toBe(longContent);
});
it('处理空字符串消息', async () => {
const session = await createTrackedSession({ name: 'Test' });
const message = await manager.addMessage(session.id, {
role: 'user',
content: '',
});
expect(message?.content).toBe('');
});
it('多个会话独立的消息', async () => {
const session1 = await createTrackedSession({ name: 'Session 1' });
const session2 = await createTrackedSession({ name: 'Session 2' });
await manager.addMessage(session1.id, { role: 'user', content: 'Message for session 1' });
await manager.addMessage(session2.id, { role: 'user', content: 'Message for session 2' });
const messages1 = manager.getMessages(session1.id);
const messages2 = manager.getMessages(session2.id);
expect(messages1.length).toBe(1);
expect(messages2.length).toBe(1);
expect(messages1[0].content).toBe('Message for session 1');
expect(messages2[0].content).toBe('Message for session 2');
});
});
});