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:
@@ -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(),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Agent Module
|
||||
*
|
||||
* 导出 Agent 适配器
|
||||
*/
|
||||
|
||||
export {
|
||||
initCore,
|
||||
isCoreAvailable,
|
||||
getOrCreateAgent,
|
||||
destroyAgent,
|
||||
processMessage,
|
||||
cancelProcessing,
|
||||
getAgentStats,
|
||||
} from './adapter.js';
|
||||
Reference in New Issue
Block a user