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';
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Auth Module
|
||||
*
|
||||
* 导出认证相关功能
|
||||
*/
|
||||
|
||||
export {
|
||||
// 类型
|
||||
type AuthConfig,
|
||||
type AuthContext,
|
||||
// Token 操作
|
||||
generateToken,
|
||||
maskToken,
|
||||
validateToken,
|
||||
extractToken,
|
||||
// 配置
|
||||
initAuth,
|
||||
getAuthConfig,
|
||||
addToken,
|
||||
removeToken,
|
||||
setAuthEnabled,
|
||||
// 中间件
|
||||
authMiddleware,
|
||||
getAuthContext,
|
||||
} from './token.js';
|
||||
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* Token Authentication
|
||||
*
|
||||
* 简单的 Token 认证机制
|
||||
* - 本地模式 (127.0.0.1): 无需认证
|
||||
* - 远程模式: 需要 Bearer Token
|
||||
*/
|
||||
|
||||
import { createMiddleware } from 'hono/factory';
|
||||
import type { Context, Next } from 'hono';
|
||||
|
||||
// ============================================================================
|
||||
// 类型定义
|
||||
// ============================================================================
|
||||
|
||||
export interface AuthConfig {
|
||||
/** 是否启用认证 (远程模式下自动启用) */
|
||||
enabled: boolean;
|
||||
/** 有效的 token 列表 */
|
||||
tokens: string[];
|
||||
/** 跳过认证的路径 */
|
||||
skipPaths: string[];
|
||||
}
|
||||
|
||||
export interface AuthContext {
|
||||
/** 是否已认证 */
|
||||
authenticated: boolean;
|
||||
/** 使用的 token (脱敏) */
|
||||
tokenHint?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 默认配置
|
||||
// ============================================================================
|
||||
|
||||
const defaultConfig: AuthConfig = {
|
||||
enabled: false,
|
||||
tokens: [],
|
||||
skipPaths: ['/health', '/api/health'],
|
||||
};
|
||||
|
||||
// 当前配置
|
||||
let authConfig: AuthConfig = { ...defaultConfig };
|
||||
|
||||
// ============================================================================
|
||||
// Token 生成
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 生成随机 token
|
||||
*/
|
||||
export function generateToken(length: number = 32): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let token = '';
|
||||
const randomValues = new Uint8Array(length);
|
||||
crypto.getRandomValues(randomValues);
|
||||
for (let i = 0; i < length; i++) {
|
||||
token += chars[randomValues[i] % chars.length];
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏 token (只显示前4位和后4位)
|
||||
*/
|
||||
export function maskToken(token: string): string {
|
||||
if (token.length <= 8) {
|
||||
return '****';
|
||||
}
|
||||
return `${token.slice(0, 4)}...${token.slice(-4)}`;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 配置管理
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 初始化认证配置
|
||||
*/
|
||||
export function initAuth(config: Partial<AuthConfig> = {}): AuthConfig {
|
||||
authConfig = {
|
||||
...defaultConfig,
|
||||
...config,
|
||||
};
|
||||
return authConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取认证配置
|
||||
*/
|
||||
export function getAuthConfig(): AuthConfig {
|
||||
return { ...authConfig };
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 token
|
||||
*/
|
||||
export function addToken(token: string): void {
|
||||
if (!authConfig.tokens.includes(token)) {
|
||||
authConfig.tokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 token
|
||||
*/
|
||||
export function removeToken(token: string): void {
|
||||
const index = authConfig.tokens.indexOf(token);
|
||||
if (index !== -1) {
|
||||
authConfig.tokens.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用认证
|
||||
*/
|
||||
export function setAuthEnabled(enabled: boolean): void {
|
||||
authConfig.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为本地请求
|
||||
*/
|
||||
function isLocalRequest(c: Context): boolean {
|
||||
// 获取客户端 IP
|
||||
const forwarded = c.req.header('x-forwarded-for');
|
||||
const realIp = c.req.header('x-real-ip');
|
||||
|
||||
// 如果有代理头,检查原始 IP
|
||||
if (forwarded) {
|
||||
const clientIp = forwarded.split(',')[0].trim();
|
||||
return isLocalIP(clientIp);
|
||||
}
|
||||
|
||||
if (realIp) {
|
||||
return isLocalIP(realIp);
|
||||
}
|
||||
|
||||
// Bun/Hono 环境下获取连接 IP
|
||||
// 这需要 server 层传递,目前假设本地
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为本地 IP
|
||||
*/
|
||||
function isLocalIP(ip: string): boolean {
|
||||
return (
|
||||
ip === '127.0.0.1' ||
|
||||
ip === '::1' ||
|
||||
ip === 'localhost' ||
|
||||
ip.startsWith('192.168.') ||
|
||||
ip.startsWith('10.') ||
|
||||
ip.startsWith('172.16.') ||
|
||||
ip.startsWith('172.17.') ||
|
||||
ip.startsWith('172.18.') ||
|
||||
ip.startsWith('172.19.') ||
|
||||
ip.startsWith('172.2') ||
|
||||
ip.startsWith('172.30.') ||
|
||||
ip.startsWith('172.31.')
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 认证验证
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 验证 token
|
||||
*/
|
||||
export function validateToken(token: string): boolean {
|
||||
return authConfig.tokens.includes(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中提取 token
|
||||
*/
|
||||
export function extractToken(c: Context): string | null {
|
||||
// 1. 从 Authorization header 提取
|
||||
const authHeader = c.req.header('Authorization');
|
||||
if (authHeader?.startsWith('Bearer ')) {
|
||||
return authHeader.slice(7);
|
||||
}
|
||||
|
||||
// 2. 从 query parameter 提取 (用于 WebSocket)
|
||||
const queryToken = c.req.query('token');
|
||||
if (queryToken) {
|
||||
return queryToken;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hono 中间件
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 认证中间件
|
||||
*/
|
||||
export const authMiddleware = createMiddleware(async (c: Context, next: Next) => {
|
||||
// 检查是否跳过认证
|
||||
const path = c.req.path;
|
||||
if (authConfig.skipPaths.some((p) => path.startsWith(p))) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果认证未启用,跳过
|
||||
if (!authConfig.enabled) {
|
||||
c.set('auth', { authenticated: true } as AuthContext);
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 本地请求跳过认证
|
||||
if (isLocalRequest(c)) {
|
||||
c.set('auth', { authenticated: true } as AuthContext);
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 提取 token
|
||||
const token = extractToken(c);
|
||||
|
||||
if (!token) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Authentication required',
|
||||
message: 'Please provide a valid token in the Authorization header',
|
||||
},
|
||||
401
|
||||
);
|
||||
}
|
||||
|
||||
// 验证 token
|
||||
if (!validateToken(token)) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Invalid token',
|
||||
message: 'The provided token is not valid',
|
||||
},
|
||||
401
|
||||
);
|
||||
}
|
||||
|
||||
// 设置认证上下文
|
||||
c.set('auth', {
|
||||
authenticated: true,
|
||||
tokenHint: maskToken(token),
|
||||
} as AuthContext);
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取当前请求的认证上下文
|
||||
*/
|
||||
export function getAuthContext(c: Context): AuthContext {
|
||||
return c.get('auth') || { authenticated: false };
|
||||
}
|
||||
@@ -8,35 +8,71 @@
|
||||
* bun run packages/server/src/bin/server.ts
|
||||
* bun run packages/server/src/bin/server.ts --port 8080
|
||||
* bun run packages/server/src/bin/server.ts --host 0.0.0.0 --port 3000
|
||||
* bun run packages/server/src/bin/server.ts --auth --token mytoken
|
||||
*/
|
||||
|
||||
import { app, websocket, startServer } from '../index.js';
|
||||
|
||||
// 解析命令行参数
|
||||
function parseArgs(): { port: number; host: string } {
|
||||
function parseArgs(): { port: number; host: string; auth?: boolean; token?: string } {
|
||||
const args = process.argv.slice(2);
|
||||
let port = 3000;
|
||||
let host = '127.0.0.1';
|
||||
let auth: boolean | undefined;
|
||||
let token: string | undefined;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--port' || args[i] === '-p') {
|
||||
port = parseInt(args[i + 1], 10) || 3000;
|
||||
i++;
|
||||
} else if (args[i] === '--host' || args[i] === '-h') {
|
||||
} else if (args[i] === '--host' || args[i] === '-H') {
|
||||
host = args[i + 1] || '127.0.0.1';
|
||||
i++;
|
||||
} else if (args[i] === '--auth') {
|
||||
auth = true;
|
||||
} else if (args[i] === '--no-auth') {
|
||||
auth = false;
|
||||
} else if (args[i] === '--token' || args[i] === '-t') {
|
||||
token = args[i + 1];
|
||||
i++;
|
||||
} else if (args[i] === '--help' || args[i] === '-h') {
|
||||
console.log(`
|
||||
AI Assistant Server
|
||||
|
||||
Usage:
|
||||
bun run server.ts [options]
|
||||
|
||||
Options:
|
||||
-p, --port <port> Port to listen on (default: 3000)
|
||||
-H, --host <host> Host to bind to (default: 127.0.0.1)
|
||||
--auth Enable authentication
|
||||
--no-auth Disable authentication
|
||||
-t, --token <token> Set authentication token
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
# Local development (no auth)
|
||||
bun run server.ts
|
||||
|
||||
# Remote server with auth
|
||||
bun run server.ts --host 0.0.0.0 --auth
|
||||
|
||||
# Custom token
|
||||
bun run server.ts --host 0.0.0.0 --token mysecrettoken
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
return { port, host };
|
||||
return { port, host, auth, token };
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
const { port, host } = parseArgs();
|
||||
const { port, host, auth, token } = parseArgs();
|
||||
|
||||
// 打印启动信息
|
||||
startServer({ port, host });
|
||||
// 初始化并打印启动信息
|
||||
await startServer({ port, host, auth, token });
|
||||
|
||||
// 启动 Bun 服务器
|
||||
const server = Bun.serve({
|
||||
@@ -46,7 +82,7 @@ async function main() {
|
||||
websocket,
|
||||
});
|
||||
|
||||
console.log(`Server started at http://${host}:${port}`);
|
||||
console.log(`Server running at http://${host}:${port}`);
|
||||
|
||||
// 优雅关闭
|
||||
process.on('SIGINT', () => {
|
||||
|
||||
@@ -18,6 +18,16 @@ import {
|
||||
} from './ws.js';
|
||||
import { handleSSE, getSSEStats } from './sse.js';
|
||||
import { getSessionManager } from './session/manager.js';
|
||||
import { initCore, isCoreAvailable, getAgentStats } from './agent/index.js';
|
||||
import {
|
||||
authMiddleware,
|
||||
initAuth,
|
||||
getAuthConfig,
|
||||
generateToken,
|
||||
addToken,
|
||||
setAuthEnabled,
|
||||
maskToken,
|
||||
} from './auth/index.js';
|
||||
|
||||
// 创建 Hono 应用
|
||||
const app = new Hono();
|
||||
@@ -36,15 +46,26 @@ app.use(
|
||||
})
|
||||
);
|
||||
|
||||
// 健康检查
|
||||
// 认证中间件 (在 CORS 之后)
|
||||
app.use('*', authMiddleware);
|
||||
|
||||
// 健康检查 (跳过认证)
|
||||
app.get('/health', (c) => {
|
||||
const sessionManager = getSessionManager();
|
||||
const wsStats = getConnectionStats();
|
||||
const sseStats = getSSEStats();
|
||||
const authConfig = getAuthConfig();
|
||||
|
||||
return c.json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
agent: {
|
||||
coreAvailable: isCoreAvailable(),
|
||||
},
|
||||
auth: {
|
||||
enabled: authConfig.enabled,
|
||||
tokenCount: authConfig.tokens.length,
|
||||
},
|
||||
stats: {
|
||||
sessions: sessionManager.count(),
|
||||
websocket: wsStats,
|
||||
@@ -119,6 +140,10 @@ app.onError((err, c) => {
|
||||
export interface ServerOptions {
|
||||
port?: number;
|
||||
host?: string;
|
||||
/** 是否启用认证 (远程模式自动启用) */
|
||||
auth?: boolean;
|
||||
/** 预设的 token */
|
||||
token?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,12 +160,51 @@ export function createServer(options: ServerOptions = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化服务器(加载 core 模块等)
|
||||
*/
|
||||
export async function initServer(options: ServerOptions = {}): Promise<void> {
|
||||
// 尝试加载 core 模块
|
||||
const coreLoaded = await initCore();
|
||||
if (coreLoaded) {
|
||||
console.log('[Server] Core module initialized');
|
||||
} else {
|
||||
console.warn('[Server] Core module not available, running in limited mode');
|
||||
}
|
||||
|
||||
// 初始化认证
|
||||
const { host = '127.0.0.1', auth, token } = options;
|
||||
const isRemote = host !== '127.0.0.1' && host !== 'localhost';
|
||||
const authEnabled = auth !== undefined ? auth : isRemote;
|
||||
|
||||
initAuth({
|
||||
enabled: authEnabled,
|
||||
tokens: [],
|
||||
skipPaths: ['/health', '/api/health'],
|
||||
});
|
||||
|
||||
// 如果启用认证,生成或使用提供的 token
|
||||
if (authEnabled) {
|
||||
const serverToken = token || generateToken();
|
||||
addToken(serverToken);
|
||||
console.log(`[Auth] Authentication enabled`);
|
||||
console.log(`[Auth] Token: ${serverToken}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动服务器 (Bun 环境)
|
||||
*/
|
||||
export function startServer(options: ServerOptions = {}): void {
|
||||
export async function startServer(options: ServerOptions = {}): Promise<void> {
|
||||
const { port = 3000, host = '127.0.0.1' } = options;
|
||||
|
||||
// 初始化
|
||||
await initServer(options);
|
||||
|
||||
const coreStatus = isCoreAvailable() ? '✅ Core loaded' : '⚠️ Core not available';
|
||||
const authConfig = getAuthConfig();
|
||||
const authStatus = authConfig.enabled ? '🔐 Enabled' : '🔓 Disabled';
|
||||
|
||||
console.log(`
|
||||
╔════════════════════════════════════════════╗
|
||||
║ AI Assistant Server ║
|
||||
@@ -149,6 +213,8 @@ export function startServer(options: ServerOptions = {}): void {
|
||||
║ WebSocket: ws://${host}:${port}/api/ws/:sessionId
|
||||
║ SSE: http://${host}:${port}/api/sessions/:id/events
|
||||
║ Health: http://${host}:${port}/health
|
||||
║ Agent: ${coreStatus}
|
||||
║ Auth: ${authStatus}
|
||||
╚════════════════════════════════════════════╝
|
||||
`);
|
||||
|
||||
@@ -170,4 +236,21 @@ export {
|
||||
emitFileChangeEvent,
|
||||
} from './sse.js';
|
||||
export { broadcastToSession } from './ws.js';
|
||||
export {
|
||||
initCore,
|
||||
isCoreAvailable,
|
||||
getAgentStats,
|
||||
processMessage,
|
||||
cancelProcessing,
|
||||
} from './agent/index.js';
|
||||
export {
|
||||
initAuth,
|
||||
getAuthConfig,
|
||||
generateToken,
|
||||
addToken,
|
||||
removeToken,
|
||||
setAuthEnabled,
|
||||
validateToken,
|
||||
maskToken,
|
||||
} from './auth/index.js';
|
||||
export * from './types.js';
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import type { WSContext } from 'hono/ws';
|
||||
import { getSessionManager } from './session/manager.js';
|
||||
import { processMessage, cancelProcessing } from './agent/index.js';
|
||||
import type { ClientMessage, ServerMessage } from './types.js';
|
||||
|
||||
// 存储活跃的 WebSocket 连接
|
||||
@@ -103,9 +104,10 @@ export async function handleWebSocketMessage(
|
||||
switch (message.type) {
|
||||
case 'message': {
|
||||
// 用户发送消息
|
||||
const content = message.payload?.content || '';
|
||||
const userMessage = sessionManager.addMessage(sessionId, {
|
||||
role: 'user',
|
||||
content: message.payload?.content || '',
|
||||
content,
|
||||
});
|
||||
|
||||
if (userMessage) {
|
||||
@@ -116,48 +118,22 @@ export async function handleWebSocketMessage(
|
||||
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);
|
||||
// 调用 Agent 处理消息(异步,不阻塞)
|
||||
processMessage(sessionId, content).catch((error) => {
|
||||
console.error('[WS] Agent processing error:', error);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'cancel': {
|
||||
// 取消当前操作
|
||||
// TODO: 实现取消逻辑
|
||||
cancelProcessing(sessionId);
|
||||
broadcastToSession(sessionId, {
|
||||
type: 'cancelled',
|
||||
sessionId,
|
||||
payload: { message: 'Operation cancelled' },
|
||||
});
|
||||
sessionManager.updateStatus(sessionId, 'idle');
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user