feat: 重构为 Monorepo 架构并实现 HTTP Server
架构变更: - 采用 pnpm workspaces 实现 Monorepo 结构 - 将现有代码迁移到 packages/core - 新增 packages/server HTTP 服务层 Server 功能: - REST API: 会话管理、工具管理、配置管理 - WebSocket: 实时双向通信支持 - SSE: 服务端事件推送 - Hono + Bun 作为运行时 API 端点: - GET/POST /api/sessions - 会话 CRUD - GET/POST /api/sessions/:id/messages - 消息管理 - GET /api/sessions/:id/events - SSE 事件流 - WS /api/ws/:sessionId - WebSocket 连接 - GET/POST /api/tools - 工具管理 - GET/PUT /api/config - 配置管理
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* WebSocket Handler
|
||||
*
|
||||
* 处理实时双向通信,主要用于 AI 对话流
|
||||
*/
|
||||
|
||||
import type { WSContext } from 'hono/ws';
|
||||
import { getSessionManager } from './session/manager.js';
|
||||
import type { ClientMessage, ServerMessage } from './types.js';
|
||||
|
||||
// 存储活跃的 WebSocket 连接
|
||||
const connections: Map<string, Set<WSContext>> = new Map();
|
||||
|
||||
/**
|
||||
* 获取会话的所有连接
|
||||
*/
|
||||
export function getSessionConnections(sessionId: string): Set<WSContext> {
|
||||
return connections.get(sessionId) || new Set();
|
||||
}
|
||||
|
||||
/**
|
||||
* 向会话的所有连接发送消息
|
||||
*/
|
||||
export function broadcastToSession(sessionId: string, message: ServerMessage): void {
|
||||
const conns = connections.get(sessionId);
|
||||
if (!conns) return;
|
||||
|
||||
const data = JSON.stringify(message);
|
||||
for (const ws of conns) {
|
||||
try {
|
||||
ws.send(data);
|
||||
} catch (error) {
|
||||
console.error('Failed to send message:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket 连接处理器
|
||||
*/
|
||||
export function handleWebSocket(ws: WSContext, sessionId: string): void {
|
||||
const sessionManager = getSessionManager();
|
||||
|
||||
// 验证会话
|
||||
if (!sessionManager.exists(sessionId)) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
sessionId,
|
||||
payload: { message: 'Session not found' },
|
||||
} as ServerMessage)
|
||||
);
|
||||
ws.close(4004, 'Session not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册连接
|
||||
if (!connections.has(sessionId)) {
|
||||
connections.set(sessionId, new Set());
|
||||
}
|
||||
connections.get(sessionId)!.add(ws);
|
||||
|
||||
// 更新会话状态
|
||||
sessionManager.updateStatus(sessionId, 'active');
|
||||
|
||||
// 发送连接成功消息
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'connected',
|
||||
sessionId,
|
||||
payload: { message: 'Connected to session' },
|
||||
} as ServerMessage)
|
||||
);
|
||||
|
||||
console.log(`[WS] Client connected to session: ${sessionId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket 消息处理器
|
||||
*/
|
||||
export async function handleWebSocketMessage(
|
||||
ws: WSContext,
|
||||
sessionId: string,
|
||||
data: unknown
|
||||
): Promise<void> {
|
||||
const sessionManager = getSessionManager();
|
||||
|
||||
try {
|
||||
// 处理不同类型的数据
|
||||
let text: string;
|
||||
if (typeof data === 'string') {
|
||||
text = data;
|
||||
} else if (data instanceof ArrayBuffer || data instanceof SharedArrayBuffer) {
|
||||
text = new TextDecoder().decode(data as ArrayBuffer);
|
||||
} else if (data instanceof Blob) {
|
||||
text = await data.text();
|
||||
} else {
|
||||
text = String(data);
|
||||
}
|
||||
|
||||
const message: ClientMessage = JSON.parse(text);
|
||||
|
||||
switch (message.type) {
|
||||
case 'message': {
|
||||
// 用户发送消息
|
||||
const userMessage = sessionManager.addMessage(sessionId, {
|
||||
role: 'user',
|
||||
content: message.payload?.content || '',
|
||||
});
|
||||
|
||||
if (userMessage) {
|
||||
// 广播用户消息
|
||||
broadcastToSession(sessionId, {
|
||||
type: 'message_received',
|
||||
sessionId,
|
||||
payload: userMessage,
|
||||
});
|
||||
|
||||
// 更新状态为处理中
|
||||
sessionManager.updateStatus(sessionId, 'busy');
|
||||
|
||||
// TODO: 调用 Agent 处理消息并流式返回
|
||||
// 这里需要集成 core 模块的 Agent
|
||||
// const agent = createAgent();
|
||||
// for await (const chunk of agent.stream(message.payload.content)) {
|
||||
// broadcastToSession(sessionId, {
|
||||
// type: 'chunk',
|
||||
// sessionId,
|
||||
// payload: { content: chunk },
|
||||
// });
|
||||
// }
|
||||
|
||||
// 模拟响应 (后续替换为真实 Agent 调用)
|
||||
setTimeout(() => {
|
||||
const assistantMessage = sessionManager.addMessage(sessionId, {
|
||||
role: 'assistant',
|
||||
content: 'This is a placeholder response. Agent integration coming soon.',
|
||||
});
|
||||
|
||||
broadcastToSession(sessionId, {
|
||||
type: 'done',
|
||||
sessionId,
|
||||
payload: assistantMessage,
|
||||
});
|
||||
|
||||
sessionManager.updateStatus(sessionId, 'idle');
|
||||
}, 1000);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'cancel': {
|
||||
// 取消当前操作
|
||||
// TODO: 实现取消逻辑
|
||||
broadcastToSession(sessionId, {
|
||||
type: 'cancelled',
|
||||
sessionId,
|
||||
payload: { message: 'Operation cancelled' },
|
||||
});
|
||||
sessionManager.updateStatus(sessionId, 'idle');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'tool_response': {
|
||||
// 工具执行结果 (用于人工确认场景)
|
||||
// TODO: 处理工具响应
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
sessionId,
|
||||
payload: { message: `Unknown message type: ${(message as any).type}` },
|
||||
} as ServerMessage)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
sessionId,
|
||||
payload: {
|
||||
message: error instanceof Error ? error.message : 'Failed to process message',
|
||||
},
|
||||
} as ServerMessage)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket 关闭处理器
|
||||
*/
|
||||
export function handleWebSocketClose(ws: WSContext, sessionId: string): void {
|
||||
const conns = connections.get(sessionId);
|
||||
if (conns) {
|
||||
conns.delete(ws);
|
||||
if (conns.size === 0) {
|
||||
connections.delete(sessionId);
|
||||
// 当没有连接时,更新会话状态
|
||||
const sessionManager = getSessionManager();
|
||||
sessionManager.updateStatus(sessionId, 'idle');
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[WS] Client disconnected from session: ${sessionId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接统计
|
||||
*/
|
||||
export function getConnectionStats(): { sessions: number; connections: number } {
|
||||
let totalConnections = 0;
|
||||
for (const conns of connections.values()) {
|
||||
totalConnections += conns.size;
|
||||
}
|
||||
return {
|
||||
sessions: connections.size,
|
||||
connections: totalConnections,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user