refactor(server): 将 Core 模块从动态导入改为静态导入

- 移除 adapter.ts 中约 160 行冗余接口定义
- 简化 initCore 函数,改为初始化检查逻辑
- 简化 getOrCreateAgent,直接使用 ConfigurationError 类
- 更新缓存类型注解使用 Core 导出的类型
- 简化事件订阅代码,直接使用 agentEventEmitter
- 在 Core index.ts 中添加 agentEventEmitter 导出
- 更新测试文件适配静态导入模式
This commit is contained in:
2025-12-16 19:54:20 +08:00
parent 08d481483c
commit 026429cb2f
6 changed files with 357 additions and 735 deletions
+85 -247
View File
@@ -3,8 +3,6 @@
*
* 将 core 模块的 Agent 适配到 Server 环境
* 处理流式输出、事件推送等
*
* 使用接口定义避免直接依赖 @ai-assistant/core 类型
*/
import type { SessionStatus } from '../types.js';
@@ -12,92 +10,29 @@ import { getSessionManager } from '../session/manager.js';
import { broadcastToSession } from '../ws.js';
import { emitStatusEvent, emitLogEvent } from '../sse.js';
import { createServerPermissionCallback, setSessionAutoApprove } from '../permission/handler.js';
import { getConfig } from '../routes/config.js';
// Core 模块静态导入
import {
Agent,
SessionManager,
toolRegistry,
loadConfig,
getPermissionManager,
getProviderRegistry,
agentRegistry,
agentEventEmitter,
ConfigurationError,
type AgentChatOptions,
type ToolStartInfo,
type ToolEndInfo,
type TokenUsage,
type DetailedCompressionResult,
} from '@ai-assistant/core';
// ============================================================================
// Core 模块接口定义(避免直接依赖 @ai-assistant/core 类型)
// 类型定义
// ============================================================================
/**
* Token 使用情况接口
*/
export interface TokenUsage {
input: number;
contextLimit: number;
available: number;
usagePercent: number;
}
/**
* 压缩结果接口
*/
export interface CompressionResult {
type: 'prune' | 'compaction' | 'both' | 'none';
status: 'success' | 'noop' | 'failed_empty_summary' | 'failed_token_inflated' | 'failed_error';
freedTokens: number;
error?: string;
originalTokens?: number;
summaryTokens?: number;
}
/**
* Chat 返回结果
*/
interface ChatResult {
text: string;
messages: unknown[];
}
/**
* Session Manager 接口(Core 的 SessionManager
*/
interface SessionManagerInstance {
init(workdir: string): Promise<unknown>;
getSession(): { id: string; messages: unknown[] } | null;
setMessages(messages: unknown[]): Promise<void>;
setDiscoveredTools(tools: string[]): Promise<void>;
save(): Promise<void>;
close(): Promise<void>;
restoreSession(sessionId: string): Promise<unknown | null>;
}
/**
* Session Manager 构造函数接口
*/
interface SessionManagerConstructor {
new (): SessionManagerInstance;
}
/**
* 工具开始信息
*/
interface ToolStartInfo {
id: string;
toolName: string;
args: Record<string, unknown>;
}
/**
* 工具结束信息
*/
interface ToolEndInfo {
id: string;
status: 'completed' | 'error';
result?: unknown;
error?: string;
duration?: number;
}
/**
* Chat 选项接口
*/
interface ChatOptions {
onStream?: (chunk: string) => void;
onToolStart?: (info: ToolStartInfo) => void;
onToolEnd?: (info: ToolEndInfo) => void;
abortSignal?: AbortSignal;
}
/**
* Agent 模式选项
*/
@@ -109,111 +44,37 @@ export interface AgentModeOptions {
}
/**
* Agent 实例接口
* 压缩结果接口(简化版)
*/
interface AgentInstance {
setRegistry(registry: unknown): void;
setSessionManager(manager: SessionManagerInstance): void;
chat(message: string, options?: ChatOptions): Promise<ChatResult>;
getToolCount(): { core: number; discovered: number; total: number };
getContextUsageFormatted(): string;
getContextUsage(): TokenUsage;
compactHistory(): Promise<{ freedTokens: number; type: string }>;
getCompressionManager(): {
shouldCompress(messages: unknown[]): boolean;
};
getHistory(): unknown[];
setAgentMode?(mode: 'build' | 'plan'): void;
/** 切换模式(保留对话历史) */
switchMode?(mode: 'build' | 'plan', preserveHistory?: boolean): void;
setAutoApprove?(config: { file?: { write?: 'allow'; edit?: 'allow' } }): void;
clearAutoApprove?(): void;
export interface CompressionResult {
type: 'prune' | 'compaction' | 'both' | 'none';
status: 'success' | 'noop' | 'failed_empty_summary' | 'failed_token_inflated' | 'failed_error';
freedTokens: number;
error?: string;
originalTokens?: number;
summaryTokens?: number;
}
/**
* Agent 构造函数接口
* 上下文使用情况(带额外字段)
*/
interface AgentConstructor {
new (config: unknown): AgentInstance;
}
/**
* Tool Registry 接口
*/
interface ToolRegistry {
getCoreTools(): unknown[];
getAllTools(): unknown[];
}
/**
* Permission Manager 接口
*/
interface PermissionManager {
setAskCallback(callback: (ctx: unknown) => Promise<{ allow: boolean; remember?: boolean }>): void;
}
/**
* Provider Registry 接口
*/
interface ProviderRegistryInterface {
init(): Promise<void>;
isInitialized(): boolean;
}
/**
* Agent Registry 接口
*/
interface AgentRegistryInterface {
init(): Promise<void>;
isInitialized(): boolean;
}
/**
* 子 Agent 事件类型
*/
interface SubagentEvent {
type: 'subagent:start' | 'subagent:end' | 'subagent:stream' | 'subagent:tool_start' | 'subagent:tool_end';
sessionId: string;
agentId: string;
[key: string]: unknown;
}
/**
* Agent Event Emitter 接口
*/
interface AgentEventEmitterInterface {
on(sessionId: string, listener: (event: SubagentEvent) => void): () => void;
off(sessionId: string, listener: (event: SubagentEvent) => void): void;
clear(sessionId: string): void;
}
/**
* Core 模块接口
*/
interface CoreModule {
Agent: AgentConstructor;
SessionManager: SessionManagerConstructor;
toolRegistry: ToolRegistry;
loadConfig: () => unknown;
saveConfig: (config: Record<string, unknown>) => void;
getPermissionManager: (projectRoot?: string) => PermissionManager;
getProviderRegistry: () => ProviderRegistryInterface;
agentRegistry: AgentRegistryInterface;
agentEventEmitter?: AgentEventEmitterInterface;
export interface ContextUsageInfo extends TokenUsage {
formatted: string;
shouldCompress: boolean;
}
// ============================================================================
// 模块状态
// ============================================================================
// Core 模块引用
let coreModule: CoreModule | null = null;
// Core 模块初始化状态
let coreInitialized = false;
// Agent 实例缓存(每个 session 一个)
const agentCache: Map<string, AgentInstance> = new Map();
const agentCache: Map<string, Agent> = new Map();
// SessionManager 实例缓存(每个 session 一个)
const sessionManagerCache: Map<string, SessionManagerInstance> = new Map();
const sessionManagerCache: Map<string, SessionManager> = new Map();
// AbortController 缓存(每个 session 一个,用于取消正在进行的请求)
const abortControllerCache: Map<string, AbortController> = new Map();
@@ -229,37 +90,27 @@ let lastConfigError: { provider: string; message: string } | null = null;
* 初始化 core 模块
*/
export async function initCore(): Promise<boolean> {
if (coreInitialized) return true;
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;
}
// 初始化 ProviderRegistry(加载用户配置)
const providerRegistry = core.getProviderRegistry();
if (!providerRegistry.isInitialized()) {
await providerRegistry.init();
const providerReg = getProviderRegistry();
if (!providerReg.isInitialized()) {
await providerReg.init();
console.log('[Agent] ProviderRegistry initialized');
}
// 初始化 AgentRegistry(加载用户自定义 Agent 配置)
const agentRegistry = core.agentRegistry;
if (!agentRegistry.isInitialized()) {
await agentRegistry.init();
console.log('[Agent] AgentRegistry initialized');
}
coreModule = core;
console.log('[Agent] Core module loaded');
coreInitialized = true;
console.log('[Agent] Core module initialized');
return true;
} catch (error) {
console.warn('[Agent] Core module not available:', error);
console.error('[Agent] Core initialization failed:', error);
return false;
}
}
@@ -268,20 +119,21 @@ export async function initCore(): Promise<boolean> {
* 检查 core 模块是否可用
*/
export function isCoreAvailable(): boolean {
return coreModule !== null;
return coreInitialized;
}
/**
* 获取或创建 Agent 实例
*
* @returns Agent 实例,或 nullCore 不可用或配置错误)
* @returns Agent 实例,或 null(配置错误)
*/
export async function getOrCreateAgent(sessionId: string): Promise<AgentInstance | null> {
export async function getOrCreateAgent(sessionId: string): Promise<Agent | null> {
// 清除之前的配置错误
lastConfigError = null;
if (!coreModule) {
return null;
// 确保 Core 已初始化
if (!coreInitialized) {
await initCore();
}
// 检查缓存
@@ -290,10 +142,10 @@ export async function getOrCreateAgent(sessionId: string): Promise<AgentInstance
}
try {
// 创建新 Agent(可能抛出 ConfigurationError
const config = coreModule.loadConfig();
const agent = new coreModule.Agent(config);
agent.setRegistry(coreModule.toolRegistry);
// 创建新 Agent
const config = loadConfig();
const agent = new Agent(config);
agent.setRegistry(toolRegistry);
// 默认使用 Build 模式
if (agent.setAgentMode) {
@@ -301,7 +153,7 @@ export async function getOrCreateAgent(sessionId: string): Promise<AgentInstance
}
// 设置权限回调,通过 WebSocket 请求用户确认
const permissionManager = coreModule.getPermissionManager();
const permissionManager = getPermissionManager();
permissionManager.setAskCallback(createServerPermissionCallback(sessionId));
// 创建并初始化 SessionManager(用于消息持久化)
@@ -313,11 +165,10 @@ export async function getOrCreateAgent(sessionId: string): Promise<AgentInstance
agentCache.set(sessionId, agent);
return agent;
} catch (error) {
// 检测配置错误(通过 error.name 识别,避免直接依赖 Core 类型)
if (error instanceof Error && error.name === 'ConfigurationError') {
const configError = error as Error & { provider?: string };
// 检测配置错误
if (error instanceof ConfigurationError) {
lastConfigError = {
provider: configError.provider || 'unknown',
provider: error.provider || 'unknown',
message: error.message,
};
console.warn(`[Agent] Configuration error: ${error.message}`);
@@ -331,11 +182,7 @@ export async function getOrCreateAgent(sessionId: string): Promise<AgentInstance
/**
* 获取或创建 SessionManager 实例
*/
async function getOrCreateSessionManager(sessionId: string): Promise<SessionManagerInstance | null> {
if (!coreModule) {
return null;
}
async function getOrCreateSessionManager(sessionId: string): Promise<SessionManager | null> {
// 检查缓存
if (sessionManagerCache.has(sessionId)) {
return sessionManagerCache.get(sessionId)!;
@@ -348,7 +195,7 @@ async function getOrCreateSessionManager(sessionId: string): Promise<SessionMana
const workdir = session?.workdir || process.cwd();
// 创建新的 Core SessionManager
const sessionMgr = new coreModule.SessionManager();
const sessionMgr = new SessionManager();
// 初始化(这会创建或加载项目)
await sessionMgr.init(workdir);
@@ -356,7 +203,7 @@ async function getOrCreateSessionManager(sessionId: string): Promise<SessionMana
// 尝试恢复指定的 session(如果存在)
const restored = await sessionMgr.restoreSession(sessionId);
if (restored) {
console.log(`[Agent] Restored session ${sessionId} with ${(restored as { messages: unknown[] }).messages?.length || 0} messages`);
console.log(`[Agent] Restored session ${sessionId} with ${restored.messages?.length || 0} messages`);
}
sessionManagerCache.set(sessionId, sessionMgr);
@@ -430,8 +277,8 @@ export async function processMessage(
return;
}
// Core 模块不可用,返回占位响应
const errorContent = 'Agent core module not available. Please build @ai-assistant/core first.';
// Core 模块初始化失败,返回占位响应
const errorContent = 'Agent core module initialization failed. Please check logs for details.';
broadcastToSession(sessionId, {
type: 'chunk',
sessionId,
@@ -466,25 +313,22 @@ export async function processMessage(
setSessionAutoApprove(sessionId, null);
}
// 订阅子 Agent 事件(如果可用)
let unsubscribeSubagentEvents: (() => void) | null = null;
if (coreModule?.agentEventEmitter) {
unsubscribeSubagentEvents = coreModule.agentEventEmitter.on(sessionId, (event: SubagentEvent) => {
// 检查是否已取消
if (abortController.signal.aborted) return;
// 订阅子 Agent 事件
const unsubscribeSubagentEvents = agentEventEmitter.on(sessionId, (event) => {
// 检查是否已取消
if (abortController.signal.aborted) return;
// 转发子 Agent 事件到前端
broadcastToSession(sessionId, {
type: event.type,
sessionId,
payload: event,
});
// 转发子 Agent 事件到前端
broadcastToSession(sessionId, {
type: event.type,
sessionId,
payload: event,
});
}
});
try {
// 调用 Agent 的 chat 方法,使用流式回调和 AbortSignal
const result = await agent.chat(content, {
const chatOptions: AgentChatOptions = {
onStream: (chunk: string) => {
// 检查是否已取消
if (abortController.signal.aborted) return;
@@ -504,7 +348,7 @@ export async function processMessage(
}
}
},
onToolStart: (info) => {
onToolStart: (info: ToolStartInfo) => {
// 检查是否已取消
if (abortController.signal.aborted) return;
@@ -519,7 +363,7 @@ export async function processMessage(
},
});
},
onToolEnd: (info) => {
onToolEnd: (info: ToolEndInfo) => {
// 检查是否已取消
if (abortController.signal.aborted) return;
@@ -537,7 +381,9 @@ export async function processMessage(
});
},
abortSignal: abortController.signal,
});
};
const result = await agent.chat(content, chatOptions);
// 消息已由 Core Agent 自动持久化,这里只更新 Server 端的会话计数
const session = sessionManager.get(sessionId);
@@ -591,9 +437,7 @@ export async function processMessage(
emitLogEvent(sessionId, 'error', errorMessage);
} finally {
// 取消子 Agent 事件订阅
if (unsubscribeSubagentEvents) {
unsubscribeSubagentEvents();
}
unsubscribeSubagentEvents();
// 清理 AbortController
abortControllerCache.delete(sessionId);
sessionManager.updateStatus(sessionId, 'idle' as SessionStatus);
@@ -631,7 +475,7 @@ export function getAgentStats(sessionId: string): {
toolCount?: { core: number; discovered: number; total: number };
contextUsage?: string;
} {
if (!coreModule) {
if (!coreInitialized) {
return { available: false };
}
@@ -710,19 +554,11 @@ async function generateSessionTitle(
// 上下文压缩 API
// ============================================================================
/**
* 上下文使用情况(带额外字段)
*/
export interface ContextUsageInfo extends TokenUsage {
formatted: string;
shouldCompress: boolean;
}
/**
* 获取会话的上下文使用情况
*/
export function getContextUsage(sessionId: string): ContextUsageInfo | null {
if (!coreModule) {
if (!coreInitialized) {
return null;
}
@@ -746,9 +582,9 @@ export function getContextUsage(sessionId: string): ContextUsageInfo | null {
*/
export async function compressContext(
sessionId: string,
force: boolean = false
_force: boolean = false
): Promise<CompressionResult | null> {
if (!coreModule) {
if (!coreInitialized) {
return null;
}
@@ -776,3 +612,5 @@ export async function compressContext(
}
}
// Re-export TokenUsage for external use
export type { TokenUsage };