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
+16 -34
View File
@@ -2,6 +2,8 @@
* WebSocket Handler 测试
*
* 测试 WebSocket 连接处理、消息路由、广播功能等
*
* 注意:消息存储已移至 Core 层,Server 的 WebSocket 只负责消息广播和路由
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
@@ -9,7 +11,6 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
// Create mock functions
const mockExists = vi.fn();
const mockUpdateStatus = vi.fn();
const mockAddMessage = vi.fn();
const mockGet = vi.fn();
// Mock dependencies before imports
@@ -17,7 +18,6 @@ vi.mock('../../src/session/manager.js', () => ({
getSessionManager: vi.fn(() => ({
exists: mockExists,
updateStatus: mockUpdateStatus,
addMessage: mockAddMessage,
get: mockGet,
})),
}));
@@ -60,7 +60,6 @@ describe('WebSocket Handler', () => {
beforeEach(() => {
vi.clearAllMocks();
mockExists.mockReturnValue(false);
mockAddMessage.mockReturnValue({ id: 'msg-1', role: 'user', content: '', timestamp: Date.now() });
});
describe('handleWebSocket - 连接处理', () => {
@@ -110,6 +109,8 @@ describe('WebSocket Handler', () => {
it('处理 message 类型消息', async () => {
const ws = createMockWSContext();
handleWebSocket(ws as any, 'session-1');
const message = JSON.stringify({
type: 'message',
payload: { content: 'Hello AI' },
@@ -117,10 +118,11 @@ describe('WebSocket Handler', () => {
await handleWebSocketMessage(ws as any, 'session-1', message);
expect(mockAddMessage).toHaveBeenCalledWith('session-1', {
role: 'user',
content: 'Hello AI',
});
// 应该广播 message_received 确认
expect(ws.send).toHaveBeenCalledWith(
expect.stringContaining('"type":"message_received"')
);
// 应该调用 processMessage
expect(processMessage).toHaveBeenCalledWith('session-1', 'Hello AI');
});
@@ -175,6 +177,7 @@ describe('WebSocket Handler', () => {
it('处理 ArrayBuffer 数据', async () => {
const ws = createMockWSContext();
handleWebSocket(ws as any, 'session-1');
const message = { type: 'message', payload: { content: 'ArrayBuffer test' } };
const encoder = new TextEncoder();
@@ -182,14 +185,13 @@ describe('WebSocket Handler', () => {
await handleWebSocketMessage(ws as any, 'session-1', buffer);
expect(mockAddMessage).toHaveBeenCalledWith('session-1', {
role: 'user',
content: 'ArrayBuffer test',
});
expect(processMessage).toHaveBeenCalledWith('session-1', 'ArrayBuffer test');
});
it('空 content 处理正确', async () => {
const ws = createMockWSContext();
handleWebSocket(ws as any, 'session-1');
const message = JSON.stringify({
type: 'message',
payload: {},
@@ -197,24 +199,19 @@ describe('WebSocket Handler', () => {
await handleWebSocketMessage(ws as any, 'session-1', message);
expect(mockAddMessage).toHaveBeenCalledWith('session-1', {
role: 'user',
content: '',
});
expect(processMessage).toHaveBeenCalledWith('session-1', '');
});
it('处理 Blob 数据', async () => {
const ws = createMockWSContext();
handleWebSocket(ws as any, 'session-1');
const message = { type: 'message', payload: { content: 'Blob test' } };
const blob = new Blob([JSON.stringify(message)]);
await handleWebSocketMessage(ws as any, 'session-1', blob);
expect(mockAddMessage).toHaveBeenCalledWith('session-1', {
role: 'user',
content: 'Blob test',
});
expect(processMessage).toHaveBeenCalledWith('session-1', 'Blob test');
});
it('处理非标准数据类型(转为字符串)', async () => {
@@ -229,21 +226,6 @@ describe('WebSocket Handler', () => {
expect(cancelProcessing).toHaveBeenCalledWith('session-1');
});
it('addMessage 返回 null 时不调用 processMessage', async () => {
const ws = createMockWSContext();
mockAddMessage.mockReturnValue(null);
const message = JSON.stringify({
type: 'message',
payload: { content: 'Test' },
});
await handleWebSocketMessage(ws as any, 'session-1', message);
expect(mockAddMessage).toHaveBeenCalled();
expect(processMessage).not.toHaveBeenCalled();
});
it('处理 tool_response 类型消息(TODO 场景)', async () => {
const ws = createMockWSContext();
const message = JSON.stringify({