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:
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user