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
+25
View File
@@ -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';
+263
View File
@@ -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 };
}