UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

165 lines (164 loc) 5.71 kB
/** * AgentEventBus — Agent 间事件通信总线 * * 借鉴 AutoGen Core Event-Driven 架构 + RxJS Observable 模式: * - Agent 间松耦合通信(publish/subscribe) * - 支持同步和异步事件处理 * - 内置事件过滤、优先级、TTL * - 支持 request/reply 模式(Agent 间 RPC) * * @module AgentEventBus */ import { EventEmitter } from 'node:events'; import Logger from '#infra/logging/Logger.js'; /** 标准事件类型 */ export const AgentEvents = Object.freeze({ // ── 生命周期 ── AGENT_CREATED: 'agent:created', AGENT_STARTED: 'agent:started', AGENT_COMPLETED: 'agent:completed', AGENT_FAILED: 'agent:failed', AGENT_ABORTED: 'agent:aborted', // ── 执行 ── TOOL_CALL_START: 'tool:call:start', TOOL_CALL_END: 'tool:call:end', LLM_CALL_START: 'llm:call:start', LLM_CALL_END: 'llm:call:end', STEP_COMPLETED: 'step:completed', // ── Agent 间交互 ── HANDOFF_REQUEST: 'handoff:request', HANDOFF_ACCEPT: 'handoff:accept', HANDOFF_RESULT: 'handoff:result', // ── 进度 ── PROGRESS: 'progress', THINKING: 'thinking', STREAM_DELTA: 'stream:delta', // ── 外部触发 ── USER_INPUT: 'user:input', LARK_MESSAGE: 'lark:message', SCAN_REQUEST: 'scan:request', }); export class AgentEventBus extends EventEmitter { static #instance = null; #logger; /** topic → handlers */ #subscriptions = new Map(); /** >} */ #pendingReplies = new Map(); /** 事件计数 */ #eventCount = 0; constructor() { super(); this.setMaxListeners(100); this.#logger = Logger.getInstance(); } /** 获取全局单例 */ static getInstance() { if (!AgentEventBus.#instance) { AgentEventBus.#instance = new AgentEventBus(); } return AgentEventBus.#instance; } /** 重置单例(测试用) */ static resetInstance() { if (AgentEventBus.#instance) { AgentEventBus.#instance.removeAllListeners(); AgentEventBus.#instance.#subscriptions.clear(); AgentEventBus.#instance.#pendingReplies.clear(); } AgentEventBus.#instance = null; } // ─── 发布 ──────────────────────────────── /** * 发布事件(广播) * @param type 事件类型 * @param payload 事件数据 * @param [opts.source] 发送者 agentId * @param [opts.target] 目标 agentId * @param [opts.correlationId] 关联 ID */ publish(type, payload = {}, opts = {}) { this.#eventCount++; const event = { type, source: opts.source || 'system', target: opts.target || null, payload, timestamp: Date.now(), correlationId: opts.correlationId || null, }; // 发射到 EventEmitter(通用监听) this.emit(type, event); this.emit('*', event); // 全局监听 // 发射到 topic 订阅者 const handlers = this.#subscriptions.get(type) || []; for (const handler of handlers) { try { handler(event); } catch (err) { this.#logger.warn(`[AgentEventBus] Handler error on ${type}: ${err instanceof Error ? err.message : String(err)}`); } } // 检查是否有 pending reply if (opts.correlationId && this.#pendingReplies.has(opts.correlationId)) { const pending = this.#pendingReplies.get(opts.correlationId); clearTimeout(pending.timer); this.#pendingReplies.delete(opts.correlationId); pending.resolve(event); } } /** * 订阅事件 * @param type 事件类型 * @param handler 处理函数 (event) => void * @returns 取消订阅函数 */ subscribe(type, handler) { if (!this.#subscriptions.has(type)) { this.#subscriptions.set(type, []); } this.#subscriptions.get(type).push(handler); return () => { const handlers = this.#subscriptions.get(type); if (handlers) { const idx = handlers.indexOf(handler); if (idx >= 0) { handlers.splice(idx, 1); } } }; } /** * Request/Reply 模式 — 发送请求并等待响应 * @param requestType 请求事件类型 * @param payload 请求数据 * @param [opts.timeout=30000] 超时毫秒 * @param [opts.source] 发送者 * @returns 响应事件 */ async request(requestType, payload = {}, opts = {}) { const correlationId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; const timeout = opts.timeout || 30_000; const { promise, resolve, reject } = Promise.withResolvers(); const timer = setTimeout(() => { this.#pendingReplies.delete(correlationId); reject(new Error(`AgentEventBus request timeout: ${requestType} (${timeout}ms)`)); }, timeout); this.#pendingReplies.set(correlationId, { resolve, reject, timer }); this.publish(requestType, payload, { source: opts.source, correlationId, }); return promise; } /** 获取事件统计 */ getStats() { return { totalEvents: this.#eventCount, subscriptionTopics: this.#subscriptions.size, pendingReplies: this.#pendingReplies.size, }; } } export default AgentEventBus;