feat: 完善 Server 层并添加 CLI 和 Web 前端

Server 层增强:
- 添加 Agent 适配层,支持动态加载 core 模块
- 实现 Token 认证机制,支持本地/远程模式
- WebSocket 集成 Agent 实时对话

CLI 模块 (packages/cli):
- serve 命令启动 HTTP Server
- attach 命令连接远程 Server
- API Client 封装

Web 前端 (packages/web):
- React 18 + Vite + Tailwind CSS
- 会话管理侧边栏
- WebSocket 实时聊天界面
- 流式消息显示
This commit is contained in:
2025-12-12 11:22:25 +08:00
parent 5e32375f0e
commit 168996a475
35 changed files with 4028 additions and 52 deletions
+249
View File
@@ -0,0 +1,249 @@
/**
* Agent Adapter
*
* 将 core 模块的 Agent 适配到 Server 环境
* 处理流式输出、事件推送等
*
* 使用接口定义避免直接依赖 @ai-assistant/core 类型
*/
import type { SessionStatus } from '../types.js';
import { getSessionManager } from '../session/manager.js';
import { broadcastToSession } from '../ws.js';
import { emitStatusEvent, emitLogEvent } from '../sse.js';
// ============================================================================
// Core 模块接口定义(避免直接依赖 @ai-assistant/core 类型)
// ============================================================================
/**
* Agent 实例接口
*/
interface AgentInstance {
setRegistry(registry: unknown): void;
chat(message: string, onStream?: (chunk: string) => void): Promise<string>;
getToolCount(): { core: number; discovered: number; total: number };
getContextUsageFormatted(): string;
}
/**
* Agent 构造函数接口
*/
interface AgentConstructor {
new (config: unknown): AgentInstance;
}
/**
* Tool Registry 接口
*/
interface ToolRegistry {
getCoreTools(): unknown[];
getAllTools(): unknown[];
}
/**
* Core 模块接口
*/
interface CoreModule {
Agent: AgentConstructor;
toolRegistry: ToolRegistry;
loadConfig: () => unknown;
}
// ============================================================================
// 模块状态
// ============================================================================
// Core 模块引用
let coreModule: CoreModule | null = null;
// Agent 实例缓存(每个 session 一个)
const agentCache: Map<string, AgentInstance> = new Map();
// ============================================================================
// 公共 API
// ============================================================================
/**
* 初始化 core 模块
*/
export async function initCore(): Promise<boolean> {
try {
// 使用变量避免 TypeScript 静态分析 import 路径
const corePath = '@ai-assistant/core';
// eslint-disable-next-line @typescript-eslint/no-require-imports
const core = (await import(/* webpackIgnore: true */ corePath)) as unknown as CoreModule;
// 验证模块结构
if (!core.Agent || !core.toolRegistry || !core.loadConfig) {
console.warn('[Agent] Core module missing required exports');
return false;
}
coreModule = core;
console.log('[Agent] Core module loaded');
return true;
} catch (error) {
console.warn('[Agent] Core module not available:', error);
return false;
}
}
/**
* 检查 core 模块是否可用
*/
export function isCoreAvailable(): boolean {
return coreModule !== null;
}
/**
* 获取或创建 Agent 实例
*/
export function getOrCreateAgent(sessionId: string): AgentInstance | null {
if (!coreModule) {
return null;
}
// 检查缓存
if (agentCache.has(sessionId)) {
return agentCache.get(sessionId)!;
}
// 创建新 Agent
const config = coreModule.loadConfig();
const agent = new coreModule.Agent(config);
agent.setRegistry(coreModule.toolRegistry);
agentCache.set(sessionId, agent);
return agent;
}
/**
* 销毁 Agent 实例
*/
export function destroyAgent(sessionId: string): void {
agentCache.delete(sessionId);
}
/**
* 处理用户消息并流式返回响应
*/
export async function processMessage(sessionId: string, content: string): Promise<void> {
const sessionManager = getSessionManager();
// 更新状态
sessionManager.updateStatus(sessionId, 'busy' as SessionStatus);
emitStatusEvent(sessionId, 'processing', { message: '正在处理...' });
// 获取 Agent
const agent = getOrCreateAgent(sessionId);
if (!agent) {
// Core 模块不可用,返回占位响应
broadcastToSession(sessionId, {
type: 'chunk',
sessionId,
payload: {
content: 'Agent core module not available. Please build @ai-assistant/core first.',
},
});
const assistantMessage = sessionManager.addMessage(sessionId, {
role: 'assistant',
content: 'Agent core module not available. Please build @ai-assistant/core first.',
});
broadcastToSession(sessionId, {
type: 'done',
sessionId,
payload: assistantMessage,
});
sessionManager.updateStatus(sessionId, 'idle' as SessionStatus);
emitStatusEvent(sessionId, 'idle');
return;
}
try {
// 调用 Agent 的 chat 方法,使用流式回调
const response = await agent.chat(content, (chunk: string) => {
// 推送流式内容
broadcastToSession(sessionId, {
type: 'chunk',
sessionId,
payload: { content: chunk },
});
// 检测工具调用
if (chunk.includes('[调用工具:')) {
const match = chunk.match(/\[调用工具: (.+?)\]/);
if (match) {
emitLogEvent(sessionId, 'info', `调用工具: ${match[1]}`);
}
}
});
// 保存助手消息
const assistantMessage = sessionManager.addMessage(sessionId, {
role: 'assistant',
content: response,
});
// 发送完成消息
broadcastToSession(sessionId, {
type: 'done',
sessionId,
payload: assistantMessage,
});
emitStatusEvent(sessionId, 'idle');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
// 发送错误
broadcastToSession(sessionId, {
type: 'error',
sessionId,
payload: { message: errorMessage },
});
emitLogEvent(sessionId, 'error', errorMessage);
} finally {
sessionManager.updateStatus(sessionId, 'idle' as SessionStatus);
}
}
/**
* 取消正在进行的处理
*/
export function cancelProcessing(sessionId: string): void {
// TODO: 实现取消逻辑
// 目前 AI SDK 的 streamText 不支持取消
const sessionManager = getSessionManager();
sessionManager.updateStatus(sessionId, 'idle' as SessionStatus);
emitStatusEvent(sessionId, 'cancelled');
}
/**
* 获取 Agent 统计信息
*/
export function getAgentStats(sessionId: string): {
available: boolean;
toolCount?: { core: number; discovered: number; total: number };
contextUsage?: string;
} {
if (!coreModule) {
return { available: false };
}
const agent = agentCache.get(sessionId);
if (!agent) {
return { available: true };
}
return {
available: true,
toolCount: agent.getToolCount(),
contextUsage: agent.getContextUsageFormatted(),
};
}
+15
View File
@@ -0,0 +1,15 @@
/**
* Agent Module
*
* 导出 Agent 适配器
*/
export {
initCore,
isCoreAvailable,
getOrCreateAgent,
destroyAgent,
processMessage,
cancelProcessing,
getAgentStats,
} from './adapter.js';