UNPKG

@multi-agent/a2a

Version:
2,294 lines (2,267 loc) 79.7 kB
import * as grpc from '@grpc/grpc-js'; import { DynamicStructuredTool } from '@langchain/core/tools'; /** * IO 计量模块类型定义 * * 提供基于输入输出的 Token 计量和计费功能 */ /** * IO 承诺(由协议层生成,使用平台签名密钥) * * 承诺包含内容哈希和 Token 数量,由平台签名确保不可伪造 */ interface IOCommitment { /** 承诺唯一 ID */ commitmentId: string; /** 关联的调用链 ID */ traceId: string; /** 内容哈希(SHA-256,不含原文,保护隐私) */ contentHash: string; /** Token 数量 */ tokens: number; /** 时间戳 */ timestamp: number; /** 平台签名(证明这是协议层计算的,非伪造) */ signature: string; } /** * 已验证的 IO 指标 * * 包含输入和输出的承诺,由协议层自动生成和上报 * */ interface VerifiedIOMetrics { /** 指标唯一 ID */ metricsId: string; /** 关联的调用链 ID(整个请求链共用) */ traceId: string; /** Agent ID */ agentId: string; /** 调用的技能名称 */ skill: string; /** 调用开始时间(ISO 8601 格式) */ startedAt: string; /** 输入承诺 */ inputCommitment: IOCommitment; /** 输出承诺 */ outputCommitment: IOCommitment; /** 处理耗时(毫秒) */ duration: number; /** 调用者用户 ID(可选,由 Auth 插件解析 token 获得) */ userId?: string; } /** * Agent 定价配置 */ interface AgentPricing { /** 输入单价(分 / 1K tokens) */ inputTokenPrice: number; /** 输出单价(分 / 1K tokens) */ outputTokenPrice: number; } /** * IO 计量提供者接口 * * 平台方实现此接口,提供计量服务 * * @example * ```typescript * const provider: IOMetricsProvider = { * verifyIntegrity: async (params) => { * // 验证 SDK 完整性,返回签名密钥 * return { valid: true, signingKey: 'xxx' } * }, * reportMetrics: async (metrics) => { * // 上报指标到平台 * await fetch('/api/metrics/report', { body: JSON.stringify(metrics) }) * }, * } * ``` */ interface IOMetricsProvider { /** * 验证 SDK 完整性,获取签名密钥 * * @param params 验证参数 * @returns 验证结果和签名密钥 */ verifyIntegrity(params: { /** SDK 版本 */ sdkVersion: string; /** SDK 核心模块哈希 */ sdkHash: string; /** Agent ID */ agentId: string; }): Promise<{ /** 验证是否通过 */ valid: boolean; /** 签名密钥(验证通过时返回) */ signingKey?: string; /** 错误信息(验证失败时返回) */ error?: string; }>; /** * 上报已验证的 IO 指标 * * @param metrics 指标数据 */ reportMetrics(metrics: VerifiedIOMetrics): Promise<void>; /** * 获取 Agent 定价(可选) * * @param agentId Agent ID * @param skill 技能名称 * @returns 定价配置 */ getPricing?(agentId: string, skill: string): Promise<AgentPricing>; /** * 预调用检查(在 LLM 调用前验证用户身份和余额) * * 这是计费安全的关键:在执行任何 LLM 调用之前先验证用户有足够余额 * 如果不实现此方法,将跳过预调用检查(向后兼容) * * @param params 检查参数 * @returns 检查结果 */ preCallCheck?(params: { /** 用户 ID(从 x-user-id metadata 获取) */ userId?: string; /** Agent ID */ agentId: string; /** 调用的技能名称 */ skill: string; /** 调用链 ID */ traceId: string; }): Promise<{ /** 是否允许调用 */ allowed: boolean; /** 拒绝原因(allowed=false 时返回) */ reason?: string; /** 错误代码(allowed=false 时返回) */ code?: 'UNAUTHORIZED' | 'INSUFFICIENT_BALANCE' | 'USER_NOT_FOUND' | 'RATE_LIMITED' | string; }>; } /** * A2A v7 Framework - TypeScript Types * * 设计理念: * 1. 协议层+业务层分离:协议消息固定3种,业务消息完全开放 * 2. 扁平化消息结构:所有字段直接在Message级别 * 3. 类型安全:使用Discriminated Union提供完美的类型推断 */ /** * A2A 框架错误码 * * 用于标识框架层面的错误类型,便于客户端根据错误码进行处理 */ declare const ErrorCode: { /** Handler 被 beforeHandler 钩子中止 */ readonly HANDLER_ABORTED: "HANDLER_ABORTED"; /** Handler 执行过程中抛出异常 */ readonly HANDLER_ERROR: "HANDLER_ERROR"; /** 请求的 Skill 不存在 */ readonly SKILL_NOT_FOUND: "SKILL_NOT_FOUND"; /** call 消息格式无效(如缺少 skill 字段) */ readonly INVALID_CALL_MESSAGE: "INVALID_CALL_MESSAGE"; /** 服务器内部错误 */ readonly INTERNAL_ERROR: "INTERNAL_ERROR"; }; type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode]; /** * Message - 统一消息结构 * * 所有消息(协议消息 + 业务消息)都使用相同的数据结构 * * 设计理念: * - 协议消息和业务消息结构完全一致 * - 通过 type 字段区分消息类型 * - text 提供人类可读描述 * - data 提供结构化数据(JSON序列化) * - from 标识消息来源 Agent(协议层自动注入) * * 框架自动管理字段: * - messageId: 消息唯一ID(每条消息自动生成) * - timestamp: Unix 时间戳毫秒 * - from: 消息来源 Agent 的 AgentCard(协议层自动注入) * * gRPC Metadata 传递字段(单一数据源,通过 ctx.metadata 访问): * - x-trace-id: 链路追踪ID,格式: tr_{timestamp}_{random} * - x-session-id: 会话ID(用于跟踪对话历史) * - x-user-id: 用户ID(用于计费归属) * - x-span-id: 调用链 Span ID * * 协议消息类型(框架保留): * - 'call': 调用Agent方法 * - 'cancel': 取消执行 * - 'agent-mount': 挂载请求(Tool Agent 挂载到 Host Agent) * - 'agent-unmount': 卸载请求(Tool Agent 从 Host Agent 卸载) * * 业务消息类型(应用自定义): * - 'error': 错误消息 * - 'question': 问题 * - 'answer': 回答 * - 'progress': 进度 * - 'todolist': 任务列表 * - 'plan': 计划 * - 'done': Agent 工作流完成(携带总结信息和关键数据) * - ... 其他自定义类型 */ interface Message { messageId?: string; timestamp?: number; type: string; text: string; data?: any; from?: AgentCard; } /** * TLS 证书配置 * * 当 address 使用 a2as:// 协议时必须提供 */ interface TLSConfig { /** * 证书文件路径(PEM 格式) * * @example '/path/to/cert.pem' */ cert: string; /** * 私钥文件路径(PEM 格式) * * @example '/path/to/key.pem' */ key: string; /** * CA 证书文件路径(可选,用于客户端证书验证) * * @example '/path/to/ca.pem' */ ca?: string; } interface AgentConfig { agentId: string; name: string; version: string; description: string; /** * Agent 的网络地址 * * 格式: a2a[s]://host:port * * - a2a:// - 无 TLS * - a2as:// - TLS 加密(需要配置 tls 选项) * * @example * // 监听所有网络接口(无 TLS) * address: 'a2a://0.0.0.0:50054' * * // 只监听本地(无 TLS) * address: 'a2a://127.0.0.1:50054' * * // TLS 加密(需要配置 tls) * address: 'a2as://0.0.0.0:50054' */ address: string; /** * TLS 证书配置 * * 当 address 使用 a2as:// 协议时必须提供 * * @example * ```typescript * const server = createAgentServer({ * address: 'a2as://0.0.0.0:50054', * tls: { * cert: '/path/to/cert.pem', * key: '/path/to/key.pem', * }, * // ... * }) * ``` */ tls?: TLSConfig; skills: SkillDefinition[]; /** * 默认技能名称 * * 必须是 skills 中某个技能的 name */ defaultSkill: string; /** * IO 计量提供者(可选) * * 配置后启用协议层 IO 计量功能: * - 自动计算输入/输出 Token 数量 * - 自动生成带签名的 IO 承诺 * - 自动上报指标到平台 * * 不配置则不启用计费功能 * * @example * ```typescript * import { createMetricsProvider } from '@agent-zhipin/metrics-provider' * * const server = createAgentServer({ * agentId: 'my-agent', * // ... * metricsProvider: createMetricsProvider({ * platformUrl: 'https://agent-zhipin.com/api', * developerApiKey: 'YOUR_API_KEY' * }), * }) * ``` */ metricsProvider?: IOMetricsProvider; } interface SkillInfo { name: string; description?: string; /** * JSON Schema 格式的输入参数定义(用于 LangChain Tools) * * 当提供此字段时,该技能可以被其他 Agent 作为 LangChain Tool 调用 * * @example * inputSchema: { * type: 'object', * properties: { * path: { * type: 'string', * description: '文件路径(相对或绝对)' * }, * content: { * type: 'string', * description: '文件内容' * } * }, * required: ['path', 'content'] * } */ inputSchema?: { type: 'object'; properties: Record<string, { type: string; description?: string; enum?: any[]; default?: any; items?: any; minimum?: number; maximum?: number; }>; required?: string[]; }; /** * JSON Schema 格式的输出结果定义(可选) * * 用于描述技能的返回值结构 */ outputSchema?: { type: 'object' | 'string' | 'number' | 'boolean' | 'array'; properties?: Record<string, { type: string; description?: string; }>; items?: any; description?: string; }; } interface AgentCard { agentId: string; name: string; version: string; description: string; skills: SkillInfo[]; /** * 默认技能名称 * * 指向 skills 中的某个技能,作为 Agent 的默认入口。 * 该技能通常包含标准工作流:澄清需求 → 生成 TodoList → 执行任务 → 返回结果 * * Host Agent 在编排时会优先调用此技能。 * * 注意:defaultSkill 必须存在于 skills 数组中。 * * 示例:'execute', 'process', 'handle' */ defaultSkill: string; /** * Agent 的访问端点 * * 由 getAgentCard() RPC 自动填充,基于客户端连接时使用的地址(:authority 头) * * 示例: * - 单机:{ host: 'localhost', port: 50059 } * - Docker:{ host: 'frontend-agent', port: 50059 } * - K8s:{ host: 'frontend-agent.default.svc.cluster.local', port: 50059 } * - 代理注册:{ host: 'localhost', port: 50050, namespace: 'tool-agent' } */ endpoint: { host: string; port: number; /** * 命名空间(可选) * * 当 Agent 通过 ParasiteHostPlugin 寄生到另一个 Agent 时, * namespace 用于标识该 Agent,使调用方能够正确路由请求。 * * @example * - undefined: 原生 Agent(直接监听端口) * - 'tool-agent@user123': 通过 Host Agent 寄生的 Tool Agent */ namespace?: string; /** * 预构建的 A2A 地址(方便直接使用) * * 格式:a2a://host:port[/namespace] * * @example * - 'a2a://localhost:50059' * - 'a2a://localhost:50050/tool-agent@user123' */ address: string; }; } /** * 双向流接口 * * 使用方式: * - 发送消息:stream.send(message) * - 接收消息:for await (const message of stream) { ... } * - 监听特定消息:stream.onError(handler).onCancel(handler) */ interface BidirectionalStream { /** * 发送消息到对端 */ send: (message: Message) => void; /** * 实现 async iterator,用于接收消息 */ [Symbol.asyncIterator]: () => AsyncIterator<Message>; /** * 结束发送(half-close) */ end: () => void; /** * 取消流并清理资源 */ cancel: (reason?: string) => void; /** * 监听特定类型的消息(可选) * @param type 消息类型 * @param handler 处理函数 * @returns 返回自身,支持链式调用 */ on?: (type: string, handler: (message: Message) => void) => BidirectionalStream; } interface Context { readonly streamId: string; /** * 双向流 * * @example * // 发送业务消息(发送问题) * ctx.stream.send({ * messageId: generateId(), * timestamp: Date.now(), * type: 'question', * text: '请选择框架:\n1. React\n2. Vue', * data: encodeJSON({ questionId: 'q1', options: ['React', 'Vue'] }) * }) * * // 接收业务消息(接收回答) * for await (const message of ctx.stream) { * if (message.type === 'answer') { * const answerData = decodeJSON(message.data) * console.log('用户回答:', message.text) * console.log('关联问题:', answerData.questionId) * } * } */ readonly stream: BidirectionalStream; /** * gRPC metadata(原始访问) * * 开发者可以从 metadata 中获取任何自定义信息,例如: * - 认证 token * - 请求追踪 ID * - 会话 ID * - 任意自定义字段 * * @example * // 获取认证 token * const authToken = ctx.metadata.get('auth-token')?.[0]?.toString('utf-8') * * // 获取会话 ID * const sessionId = ctx.metadata.get('session-id')?.[0]?.toString('utf-8') */ readonly metadata: grpc.Metadata; /** * gRPC 原始调用对象(仅 gRPC 连接可用) * * 暴露底层 gRPC 调用,提供最大灵活性。常用方法: * - call.getHost(): 获取客户端请求的目标地址(如 "8.153.165.230:50054") * - call.getPeer(): 获取客户端地址 * - call.cancelled: 检查是否已取消 * * 注意:WebSocket 连接时此字段为 undefined * * @example * // 获取客户端连接的目标地址 * if (ctx.call) { * const hostAddress = ctx.call.getHost() // "8.153.165.230:50054" * } */ readonly call?: grpc.ServerDuplexStream<any, any>; /** * 获取自身的 AgentCard */ getAgentCard: () => AgentCard; /** * 从 gRPC metadata 中获取并反序列化 JSON 数据 * * @param key - metadata key * @returns 反序列化后的对象,如果不存在或解析失败则返回 undefined * * @example * // 获取自定义数据 * const customData = ctx.getMetadata<{ userId: string }>('custom-data-bin') * if (customData) { * console.log('Custom data:', customData) * } */ getMetadata: <T = any>(key: string) => T | undefined; /** * 触发当前 handler 的 call 消息 * * 包含消息基本信息: * - messageId: 消息唯一ID * - timestamp: 消息时间戳 * - type: 消息类型 * - text: 消息文本 * - data: 结构化数据 * * 注意:traceId/sessionId 等追踪字段通过 gRPC Metadata 传递,使用 ctx.metadata 访问 * * @example * // 获取 traceId 用于日志追踪(从 gRPC Metadata,内联获取) * logger.info('收到请求', { traceId: ctx.metadata.get('x-trace-id') }) * * // 调用其他 Agent 时传递 metadata * const stream = await client.call('execute', params, { * metadata: ctx.metadata.getMap() * }) */ readonly message: Message; /** * 取消信号(框架自动管理) * * 当 Client 发送 cancel 消息时,框架会自动触发此 signal 的 abort。 * Handler 可以直接将此 signal 传递给支持 AbortSignal 的 API: * - LLM 调用(LangChain) * - fetch 请求 * - A2A client.call() * * @example * const handler = async (params, ctx) => { * // 直接使用 ctx.signal,无需手动创建 AbortController * const response = await llm.invoke(messages, { * signal: ctx.signal * }) * * // 循环中检查取消状态 * while (hasMore) { * if (ctx.signal.aborted) return * await doSomething() * } * } */ readonly signal: AbortSignal; } /** * 技能 Handler 类型 * * 纯函数:接收参数和上下文,返回结果 */ type SkillHandler<TParams = any, TResult = any> = (params: TParams, ctx: Context) => Promise<TResult>; /** * Handler 钩子配置 * * 传递给 createHandler 工厂函数,用于将钩子与原始 handler 组合 */ interface HandlerHooks { beforeHandler?: BeforeHandlerHook[]; afterHandler?: AfterHandlerHook[]; } /** * 技能定义 * * 使用 handler 属性直接定义技能处理函数。 * Server 内部会自动将 handler 包装为受保护版本,确保 beforeHandler/afterHandler 钩子被正确执行。 * * @example * ```typescript * const config = { * skills: [ * { * name: 'execute', * handler: async (params, ctx) => { ... }, * description: '执行任务', * } * ] * } * ``` */ interface SkillDefinition { /** 技能名称 */ name: string; /** * 技能处理函数 * * Server 在启动时会将此 handler 包装为受保护版本, * 自动应用 beforeHandler/afterHandler 钩子。 */ handler: SkillHandler; /** 技能描述 */ description?: string; /** 输入参数 JSON Schema */ inputSchema?: SkillInfo['inputSchema']; /** 输出结果 JSON Schema */ outputSchema?: SkillInfo['outputSchema']; } /** * 钩子返回类型 * * - 'handled': 已处理,跳过默认处理器,继续下一个消息 * - 'pass': 未处理,继续调用默认处理器 * - 'exit': 已处理,退出消息循环 * - void: 等同于 'pass' */ type HookResult = 'handled' | 'pass' | 'exit' | void; /** * 消息上下文 - 在钩子间共享数据 */ interface MessageContext { /** * 用户自定义数据存储 * 可以在 beforeMessage 中存储数据,在 afterMessage 中读取 */ metadata: Map<string, any>; /** * gRPC 原始 metadata(只读) * * 用于访问客户端传递的认证信息、会话标识等 * * @example * // 获取 authorization header * const authHeader = context.grpcMetadata.get('authorization')?.[0]?.toString() * // authHeader = 'Bearer <token>' * * // 获取自定义 header * const sessionId = context.grpcMetadata.get('x-session-id')?.[0]?.toString() */ grpcMetadata: grpc.Metadata; /** * Agent 基础信息 */ agentId: string; agentName: string; /** * 消息处理开始时间(毫秒时间戳) */ startTime: number; /** * 请求的目标命名空间(可选) * * 当客户端通过 ParasiteHostPlugin 调用寄生注册的 Agent 时, * 会在 gRPC metadata 中包含 x-agent-namespace 头, * 框架会自动解析并填充到此字段。 * * @example * - undefined: 直接调用原生 Agent * - 'tool-agent@user123': 调用通过寄生注册的 Tool Agent */ namespace?: string; } /** * Before Message Hook * 在处理任何消息之前调用 * * 支持短路: * - 返回 'handled': 消息已处理,跳过后续钩子和默认处理器 * - 返回 'exit': 消息已处理,退出消息循环 * - 返回 'pass' 或 void: 继续执行后续钩子和默认处理器 */ type BeforeMessageHook = (message: Message, stream: BidirectionalStream, context: MessageContext) => Promise<HookResult>; /** * After Message Hook * 在消息处理完成后调用 */ type AfterMessageHook = (message: Message, stream: BidirectionalStream, context: MessageContext, result: 'continue' | 'exit') => Promise<void>; /** * On Call Hook * 处理 call 消息 */ type OnCallHook = (skill: string, params: any, stream: BidirectionalStream, context: MessageContext) => Promise<HookResult>; /** * On Cancel Hook * 处理 cancel 消息 */ type OnCancelHook = (stream: BidirectionalStream, context: MessageContext) => Promise<void>; /** * On Error Hook * 处理错误 */ type OnErrorHook = (error: Error, message: Message, stream: BidirectionalStream, context: MessageContext) => Promise<void>; /** * On GetAgentCard Hook * 修改 AgentCard */ type OnGetAgentCardHook = (agentCard: AgentCard, context: { agentId: string; }) => AgentCard; /** * On Start Hook * Server 启动后触发 * * 用于执行启动后的初始化逻辑(如主动挂载到远程 Agent) * * 注意: * - 此钩子在 Server 启动成功后立即调用 * - 所有 onStart 钩子会并行执行(不阻塞) * - 如果钩子执行失败,会打印错误日志但不影响 Server 运行 */ /** * SkillHandlers Map * * 技能处理函数集合,由 Server 在启动时创建 * 这些 handler 已经组合了 beforeHandler/afterHandler 钩子 * * @example * ```typescript * // 在插件中调用技能 * const handler = skillHandlers.get('execute') * if (handler) { * await handler(params, ctx) // 钩子自动执行 * } * ``` */ type SkillHandlers = Map<string, SkillHandler>; /** * On Start Hook * Server 启动后钩子 * * @param agentConfig - Agent 配置 * @param agentCard - Agent 卡片 * @param skillHandlers - 技能处理函数集合 */ type OnStartHook = (agentConfig: AgentConfig, agentCard: AgentCard, skillHandlers: SkillHandlers) => Promise<void>; /** * On Message Hook * 通用消息处理钩子(在 beforeMessage 之后、onCall/onCancel 之前执行) * * 用于处理自定义消息类型(如 agent-mount、agent-unmount 等插件定义的消息) * * 执行顺序: * beforeMessage → onMessage → onCall/onCancel → 默认处理器 → afterMessage * * 返回值: * - 'handled': 消息已处理,跳过后续钩子和默认处理器,继续下一个消息 * - 'pass': 消息未处理,继续执行后续钩子或默认处理器 * - 'exit': 消息已处理,退出消息循环 * - void: 等同于 'pass' */ type OnMessageHook = (message: Message, stream: BidirectionalStream, context: MessageContext) => Promise<HookResult>; /** * Handler 上下文 - beforeHandler/afterHandler 钩子共享 * * 提供 Handler 执行期间的上下文信息,支持钩子间数据传递 */ interface HandlerContext { /** * 技能名称 */ skill: string; /** * 调用参数 */ params: any; /** * 链路追踪 ID * 格式: tr_{timestamp}_{random} */ traceId: string; /** * 用户 ID(从 gRPC metadata x-user-id 提取) * 用于 IO 计量的用户归属 */ userId?: string; /** * Agent ID */ agentId: string; /** * Handler 开始时间(毫秒时间戳) */ startTime: number; /** * 钩子间共享数据 * beforeHandler 可存储数据,afterHandler 可读取 */ metadata: Map<string, any>; /** * gRPC 原始 metadata(只读) * * 允许插件访问客户端传递的任意 metadata,如: * - x-parent-span-id: 父调用的 Span ID(调用链追踪) * - x-custom-header: 自定义字段 * * @example * const parentSpanId = ctx.grpcMetadata?.get('x-parent-span-id')?.[0]?.toString() */ grpcMetadata?: grpc.Metadata; /** * 取消信号(框架自动管理) * * 当用户取消请求或插件调用 abort() 时,signal.aborted 变为 true。 * 框架会在 signal.aborted 时跳过 handler 执行。 * * @example * // 在 beforeHandler 中检查(通常不需要,框架自动处理) * if (ctx.signal.aborted) return */ signal: AbortSignal; /** * 中止当前调用 * * 触发 signal.aborted = true,框架会跳过 handler 执行。 * 调用前应先通过 stream.send() 发送错误消息。 * * @example * // 认证失败时中止 * stream.send({ type: 'error', text: '请先登录' }) * ctx.abort() */ abort: () => void; } /** * beforeHandler 返回值 * * 注意:中止调用请使用 ctx.abort(),不再通过返回值控制 */ interface BeforeHandlerResult { /** * 包装后的 stream * 用于 IO 计量收集输出 */ stream?: BidirectionalStream; } /** * afterHandler 结果信息 */ interface AfterHandlerResultInfo { /** * Handler 是否执行成功 */ success: boolean; /** * 错误信息(success=false 时存在) */ error?: Error; /** * Handler 执行耗时(毫秒) */ duration: number; } /** * Before Start Hook * Server 启动前钩子(可阻塞启动) * * 用途: * - 计费平台注册 * - SDK 完整性验证 * - 获取签名密钥 * * 特性: * - 在 Server 绑定端口之前执行 * - 抛出错误可阻止 Server 启动 * - 按注册顺序依次执行 */ type BeforeStartHook = (agentConfig: AgentConfig) => Promise<void>; /** * Before Handler Hook * Handler 执行前钩子 * * 用途: * - 预调用检查(余额验证) * - 输入计量 * - 包装 stream 收集输出 * * 特性: * - 可访问 skill、params、userId 等上下文 * - 返回 { stream } 可包装 stream * - 调用 ctx.abort() 可中止调用(先发送错误消息,再调用 abort) */ type BeforeHandlerHook = (stream: BidirectionalStream, context: HandlerContext) => Promise<BeforeHandlerResult | void>; /** * After Handler Hook * Handler 执行后钩子 * * 用途: * - 输出计量 * - 指标上报 * - 调用日志 * * 特性: * - 可访问 handler 执行结果和耗时 * - 异步执行,不阻塞响应 */ type AfterHandlerHook = (stream: BidirectionalStream, context: HandlerContext, result: AfterHandlerResultInfo) => Promise<void>; /** * Server Hooks 配置 * * 每个插件提供单个钩子函数,框架自动收集多个插件的钩子并按顺序执行 * * @example * ```typescript * // 每个插件只提供单个钩子函数 * const loggingPlugin: ServerPlugin = { * hooks: { * beforeMessage: async (msg, stream, ctx) => { * console.log(msg.type) * } * } * } * * // 框架自动收集多个插件的钩子 * server * .use(loggingPlugin) // beforeMessage: fn1 * .use(metricsPlugin) // beforeMessage: fn2 * .use(authPlugin) // beforeMessage: fn3 * // 框架内部按顺序执行: [fn1, fn2, fn3] * ``` */ interface ServerHooks { /** * 生命周期钩子:Server 启动前 * 用于验证配置、注册到外部服务等 * 抛出错误可阻止 Server 启动 */ beforeStart?: BeforeStartHook; /** * 生命周期钩子:消息处理前 */ beforeMessage?: BeforeMessageHook; /** * 生命周期钩子:消息处理后 */ afterMessage?: AfterMessageHook; /** * 通用消息处理钩子(支持短路) * 在 beforeMessage 之后、onCall/onCancel 之前执行 */ onMessage?: OnMessageHook; /** * 消息类型钩子:call */ onCall?: OnCallHook; /** * 消息类型钩子:cancel */ onCancel?: OnCancelHook; /** * 错误钩子 */ onError?: OnErrorHook; /** * AgentCard 钩子 */ onGetAgentCard?: OnGetAgentCardHook; /** * 生命周期钩子:Server 启动后 * 用于执行启动后的初始化逻辑(如主动挂载到远程 Agent) */ onStart?: OnStartHook; /** * Handler 生命周期钩子:Handler 执行前 * 用于预调用检查、输入计量、包装 stream */ beforeHandler?: BeforeHandlerHook; /** * Handler 生命周期钩子:Handler 执行后 * 用于输出计量、指标上报 */ afterHandler?: AfterHandlerHook; } /** * Internal Server Hooks - 框架内部使用的合并后钩子配置 * * 与 ServerHooks 的区别: * - ServerHooks: 用户定义时只能是单个函数(用户不应手动写数组) * - InternalServerHooks: 框架合并多个插件后统一为函数数组 * * 设计决策:统一使用数组表示,即使只有一个钩子也用 [hook] 表示 * - 0个插件:undefined * - 1个插件:[fn] * - 多个插件:[fn1, fn2, fn3] * * @internal 此类型仅供框架内部使用 */ interface InternalServerHooks { beforeStart?: BeforeStartHook[]; beforeMessage?: BeforeMessageHook[]; afterMessage?: AfterMessageHook[]; onMessage?: OnMessageHook[]; onCall?: OnCallHook[]; onCancel?: OnCancelHook[]; onError?: OnErrorHook; onGetAgentCard?: OnGetAgentCardHook[]; onStart?: OnStartHook[]; beforeHandler?: BeforeHandlerHook[]; afterHandler?: AfterHandlerHook[]; } /** * Server Plugin - 中间件插件接口 * * 插件可以提供钩子函数和额外的 API * * @example * const myPlugin: ServerPlugin = { * hooks: { * beforeMessage: async (msg, stream, ctx) => { * console.log('Before:', msg.type) * } * }, * // 可选:插件提供的额外 API * getMountedAgents: () => [...], * getMountedSkills: () => [...] * } */ interface ServerPlugin { /** * 插件提供的钩子配置 */ hooks: ServerHooks; /** * 插件可以提供额外的 API(可选) * 使用索引签名允许任意方法 */ [key: string]: any; } /** * Server Builder - 支持链式调用的服务器构建器 */ interface ServerBuilder { /** * 注册插件 * 支持链式调用 * * @example * const server = createAgentServer(config) * .use(createMountManager()) * .use(loggingPlugin) */ use: (plugin: ServerPlugin) => ServerBuilder; /** * 启动服务器 */ start: () => Promise<number>; /** * 关闭服务器 */ shutdown: () => Promise<void>; /** * gRPC 服务器实例(只读) */ readonly grpcServer: grpc.Server; } interface ServerInstance { readonly grpcServer: grpc.Server; /** * 技能处理函数集合 * * 包含所有技能的 handler,已组合 beforeHandler/afterHandler 钩子 * 用于传递给 onStart 钩子,使插件(如 ParasitePlugin)能够调用技能 */ readonly skillHandlers: SkillHandlers; start: () => Promise<number>; shutdown: () => Promise<void>; } interface ClientConfig { agentId: string; /** * A2A 地址 * * 格式: a2a[s]://host:port[/namespace] * * - a2a:// - 无 TLS * - a2as:// - TLS 加密 * - /namespace - 用于调用通过 ParasiteHostPlugin 寄生注册的 Agent * * @example * // 基础格式 * address: 'a2a://localhost:50050' * * // TLS 加密 * address: 'a2as://api.example.com:50050' * * // 调用通过 Host Agent 寄生的 Tool Agent * address: 'a2a://localhost:50050/tool-agent@user123' */ address: string; /** * 自定义 gRPC metadata * * 应用层传递字符串,框架自动转换为 Buffer 传递给 gRPC。 * * @example * metadata: { * 'auth-token': 'abc123', * 'session-id': sessionId * } */ metadata?: Record<string, string>; timeout?: number; } /** * CallOptions - 客户端调用选项 * * 设计理念: * - 协议层只提供 metadata 字段,不关心具体业务字段 * - 业务层自己负责设置具体的 x-* metadata * - 保持协议层的纯净和通用性 * * 常见 metadata 约定(业务层设置): * - 'x-trace-id': 链路追踪 ID * - 'x-session-id': 会话 ID * - 'x-user-id': 用户 ID * * @example * const stream = await client.call('execute', params, { * metadata: { * 'x-trace-id': traceId, * 'x-session-id': sessionId, * 'x-user-id': userId, * }, * }) */ interface CallOptions { /** * gRPC metadata / WebSocket 消息元数据 * 用于传递认证、追踪等上下文信息 * 类型兼容 grpc.Metadata.getMap() 返回值 */ metadata?: { [key: string]: string | Buffer; }; /** * 取消信号 * 当 signal 被 abort 时,框架自动调用 stream.cancel() 取消下游 Agent * * @example * // 在 Agent handler 中调用下游 Agent * const stream = await client.call('execute', params, { * signal: ctx.signal, // 框架自动处理取消传播 * metadata: ctx.metadata.getMap() * }) */ signal?: AbortSignal; } interface ClientInstance { /** * 调用 Agent 方法,返回双向流(异步版本) * * 设计理念: * - 明确的异步操作(返回 Promise) * - 清晰的错误处理路径 * - 返回时连接已建立,可立即使用 * - 避免竞态条件和资源泄漏 * * @example * const stream = await client.call('execute', { requirement: '生成登录框' }) * * for await (const message of stream) { * // 协议消息由框架处理 * if (message.type === 'call' || message.type === 'cancel' || message.type === 'error') { * continue * } * * // 业务消息由应用处理 * switch (message.type) { * case 'question': * const answer = await promptUser(message.text) * stream.send({ * messageId: generateId(), * timestamp: Date.now(), * type: 'answer', * text: answer, * data: encodeJSON({ questionId: '...', value: answer }) * }) * break * case 'todolist': * console.log('TodoList:', message.text) * break * } * } * * @example * // 传递 metadata 实现请求追踪 * const stream = await client.call('execute', params, { * metadata: ctx.metadata.getMap(), * }) */ call: <TParams = any>(skill: string, params: TParams, options?: CallOptions) => Promise<BidirectionalStream>; /** * 建立到 Agent 的原始双向流连接(不发送任何初始消息) * * 与 call() 的区别: * - call(skill, params):自动发送初始 'call' 消息,适用于单次技能调用 * - connect():返回纯净的双向流,不发送任何消息,适用于完全透明的消息转发 * * 主要用途: * - 透明代理和消息转发 * - 需要完全控制消息流的场景 * * @example * // Host Agent 作为透明代理,转发 Frontend 和 CLI 之间的所有消息 * const cliStream = await cliClient.connect() * * // 双向转发(完全透明) * await Promise.all([ * // Frontend → CLI * (async () => { * for await (const msg of frontendStream) { * cliStream.send(msg) * } * })(), * * // CLI → Frontend * (async () => { * for await (const msg of cliStream) { * frontendStream.send(msg) * } * })() * ]) */ connect: () => Promise<BidirectionalStream>; getAgentCard: () => Promise<AgentCard>; checkHealth: () => Promise<boolean>; close: () => Promise<void>; } /** * 调用上下文 - 在 Client Hooks 间传递和修改数据 * * beforeCall 钩子可以修改 metadata 来注入 CallTicket 等信息 */ interface CallContext { /** * 目标 Agent ID */ agentId: string; /** * 调用的技能名称 */ skill: string; /** * 调用参数(可被钩子修改) */ params: any; /** * gRPC metadata(可被钩子修改) * * beforeCall 钩子可以向 metadata 中注入数据, * 这些数据会在建立 gRPC 连接时传递给 Server 端 * * @example * // 在 beforeCall 中注入 CallTicket * context.metadata['x-call-ticket'] = JSON.stringify(ticket) */ metadata: Record<string, string>; } /** * Before Call Hook * * 在建立 gRPC 连接和发送 call 消息之前执行 * * 主要用途: * - 添加认证信息 * - 修改调用参数 * - 记录日志 * - 注入指标收集等 * * 注意: * - 抛出错误可中断调用 * - 可以修改 context.metadata 来注入数据 * * @example * beforeCall: async (context) => { * context.metadata['x-trace-id'] = traceId * } */ type BeforeCallHook = (context: CallContext) => Promise<void>; /** * After Call Hook * * 在获得 stream 之后、返回给用户之前执行 * * 主要用途: * - 包装或增强 stream * - 记录调用日志 * - 监控和统计 * * @example * afterCall: async (context, stream) => { * console.log(`[${context.agentId}] 调用 ${context.skill} 成功`) * return stream // 可以返回包装后的 stream * } */ type AfterCallHook = (context: CallContext, stream: BidirectionalStream) => Promise<BidirectionalStream>; /** * Client On Error Hook * * 调用过程中发生错误时执行 * * 主要用途: * - 统一错误处理 * - 错误日志记录 * - 重试逻辑(通过重新抛出或不抛出来控制) * * @example * onError: async (error, context) => { * console.error(`调用 ${context.skill} 失败:`, error.message) * // 可以选择重新抛出或静默处理 * } */ type ClientOnErrorHook = (error: Error, context: CallContext) => Promise<void>; /** * Client Hooks 配置 * * 与 Server Hooks 类似,每个插件提供单个钩子函数, * 框架自动收集多个插件的钩子并按顺序执行 */ interface ClientHooks { /** * 调用前钩子 * 在建立连接和发送消息之前执行 */ beforeCall?: BeforeCallHook; /** * 调用后钩子 * 在获得 stream 之后、返回给用户之前执行 */ afterCall?: AfterCallHook; /** * 错误处理钩子 */ onError?: ClientOnErrorHook; } /** * Internal Client Hooks - 框架内部使用的合并后钩子配置 * * @internal 此类型仅供框架内部使用 */ interface InternalClientHooks { beforeCall?: BeforeCallHook[]; afterCall?: AfterCallHook[]; onError?: ClientOnErrorHook[]; } /** * Client Plugin - 客户端插件接口 * * 插件可以提供钩子函数和额外的 API * * @example * const billingPlugin: ClientPlugin = { * hooks: { * beforeCall: async (context) => { * const ticket = await getCallTicket(context.agentId, context.skill) * context.metadata['x-call-ticket'] = JSON.stringify(ticket) * } * }, * // 可选:插件提供的额外 API * getBalance: () => currentBalance * } */ interface ClientPlugin { /** * 插件提供的钩子配置 */ hooks: ClientHooks; /** * 插件可以提供额外的 API(可选) */ [key: string]: any; } /** * Client Builder - 支持链式调用的客户端构建器 * * @example * const client = createAgentClient(config) * .use(loggingPlugin) * .use(metricsPlugin) * * const stream = await client.call('execute', { ... }) */ interface ClientBuilder extends ClientInstance { /** * 注册插件 * 支持链式调用 */ use: (plugin: ClientPlugin) => ClientBuilder; } /** * Agent 定义工厂函数(纯函数) * * 核心设计理念: * 1. 函数式编程 - 无 class,使用工厂函数和闭包 * 2. Handler 安全 - Server 内部自动包装 handler,确保钩子被执行 * 3. 类型安全 - 使用泛型约束确保 defaultSkill 是有效技能名 */ /** * CreateAgent 选项(带类型约束) * * @template Skills - 技能定义数组类型 */ interface CreateAgentOptions<Skills extends readonly SkillDefinition[] = SkillDefinition[]> { agentId: string; name: string; version: string; description?: string; /** * Agent 的网络地址 * * 格式:a2a[s]://host:port * - a2a:// 表示无 TLS * - a2as:// 表示使用 TLS * * @example 'a2a://0.0.0.0:50050' * @example 'a2as://api.example.com:50050' */ address: string; skills: Skills; /** * 默认技能名称 * * 必填,必须是 skills 中某个技能的 name */ defaultSkill: Skills[number]['name']; /** * Agent 角色能力声明 * * - 'primary': 只能作为 primaryAgent(具备 LLM 推理能力,可协调其他 Agent) * - 'tool': 只能作为 team 成员(纯工具层,不具备 LLM 推理能力) * - 'both': 两者皆可(默认值) * * @default 'both' */ role?: 'primary' | 'tool' | 'both'; } /** * 创建 Agent 配置(对象参数风格) * * 使用泛型约束确保 defaultSkill 必须是 skills 中某个技能的 name * * @param options Agent 配置和技能 * @returns AgentConfig - Agent 完整配置 * * @example * ```typescript * const agentConfig = createAgentConfig({ * agentId: 'my-agent', * name: 'My Agent', * version: '1.0.0', * address: 'a2a://0.0.0.0:50051', * skills: [ * defineSkill('execute', handler1), * defineSkill('generate', handler2), * ], * defaultSkill: 'execute', // 类型安全:必须是 skills 中某个技能的 name * }) * ``` */ declare const createAgentConfig: <Skills extends readonly SkillDefinition[]>(options: CreateAgentOptions<Skills>) => AgentConfig; /** * 定义技能(简化配置模式) * * 核心安全机制: * - Server 在启动时自动将 handler 包装为受保护版本 * - 受保护版本自动执行 beforeHandler/afterHandler 钩子 * - 插件无法绕过钩子直接调用原始 handler * * 工作原理: * ``` * defineSkill('execute', rawHandler) * ↓ * SkillDefinition { * name: 'execute', * handler: rawHandler // 原始 handler * } * ↓ * Server.start() 调用 createProtectedHandler(name, handler, hooks, agentId) * ↓ * protectedHandler 被注册到 skillHandlers Map * ``` * * 安全性保证: * - 插件只能从 skillHandlers Map 获取 handler * - skillHandlers 中的 handler 已被 Server 包装 * - 包装后的 handler 自动执行钩子,无法绕过 * * @param name 技能名(使用字面量类型) * @param handler 原始处理函数 * @param options 可选配置(description, inputSchema, outputSchema) * @returns SkillDefinition * * @example * ```typescript * const skill = defineSkill( * 'mySkill', * async (params, ctx) => { * ctx.stream.send({ type: 'progress', text: '处理中...' }) * return { result: 'done' } * }, * { * description: 'My skill description', * inputSchema: { type: 'object', properties: { ... } }, * } * ) * ``` */ declare const defineSkill: <TName extends string, TParams = any, TResult = any>(name: TName, handler: SkillHandler<TParams, TResult>, options?: { description?: string; inputSchema?: SkillDefinition["inputSchema"]; outputSchema?: SkillDefinition["outputSchema"]; }) => SkillDefinition & { name: TName; }; /** * AgentServer - 扁平化消息架构 * * 核心特性: * - 扁平化消息结构:所有字段直接在Message级别 * - 协议层+业务层分离:协议消息固定3种,业务消息完全开放 * - Context 暴露 BidirectionalStream * - Handler 直接使用 ctx.stream.send() 和 for await */ /** * 创建AgentServer(Builder 模式) * * @param agentConfig Agent配置 * @returns ServerBuilder - 支持链式调用的构建器 * * @example * const server = createAgentServer(agentConfig) * .use(createMountManager()) * .use(loggingPlugin) * .use({ beforeMessage: simpleHook }) * * await server.start() */ declare const createAgentServer: (agentConfig: AgentConfig) => ServerBuilder; /** * AgentClient - 扁平化消息架构 * * 核心特性: * - 扁平化消息结构:所有字段直接在Message级别 * - 协议层+业务层分离:协议消息固定3种,业务消息完全开放 * - call() 直接返回 BidirectionalStream * - 直接暴露 gRPC 双向流为 async iterable * - 自动环境检测:浏览器使用 WebSocket,Node.js 使用 gRPC */ /** * 创建AgentClient(自动环境检测,支持插件) * * - 浏览器环境:自动使用 WebSocket 客户端 * - Node.js 环境:使用 gRPC 客户端 * - 支持 .use() 链式调用注册插件 * * @param config Client配置 * @returns ClientBuilder 支持链式调用的客户端构建器 * * @example * // 基础用法(无插件) * const client = createAgentClient(config) * const stream = await client.call('execute', { ... }) * * @example * // 使用插件 * const client = createAgentClient(config) * .use(loggingPlugin) * .use(metricsPlugin) * * const stream = await client.call('execute', { ... }) */ declare const createAgentClient: (config: ClientConfig) => ClientBuilder; /** * ParasiteHostPlugin - 宿主端寄生插件 * * 核心功能: * 1. 接收远程 Agent 的寄生请求(agent-register 消息) * 2. 管理已寄生 Agent 的 AgentCard 和双向流连接 * 3. 将代理请求转发给对应的寄生 Agent(基于 namespace 路由) * 4. 提供 API 查询已寄生的 Agent 列表 * * 使用场景: * - Host Agent 使用此插件,接受 Tool Agent 的寄生 * - Tool Agent 寄生后成为 "一等公民",可被 Agent Selector 选择 * - 调用方通过 namespace 指定目标 Agent,Host Agent 透明转发 * * 消息协议: * - agent-register: 寄生 Agent(data: { agentCard: AgentCard }) * - agent-unregister: 脱离寄生(无需参数,通过 stream 映射识别) * - tunneled-call: 隧道封装的 gRPC 调用(包含完整 gRPC Metadata) * - tunneled-response: 隧道封装的响应 * * 隧道封装设计(类似 VPN): * - 将完整的 gRPC 调用上下文(包括 Metadata)封装在消息中传输 * - Tool Agent 解封装后获得与云端 Agent 一致的上下文 * - 实现对 Tool Agent 完全透明的代理 * * namespace 规则: * - Client 在 agent-register 消息的 data 中传递 namespace(必传) * - Server 直接使用,不关心其格式和含义 * - 由业务层决定 namespace 的生成规则 * * @example * ```typescript * const parasiteHostPlugin = createParasiteHostPlugin() * * const server = createAgentServer(config) * .use(parasiteHostPlugin) * * await server.start() * * // 获取已注册的 Agent(包含替换后的 endpoint) * const registeredCards = parasiteHostPlugin.getRegisteredAgentCards({ * host: 'localhost', * port: 50050 * }) * ``` */ /** * ParasiteHostPlugin 接口 */ interface ParasiteHostPlugin extends ServerPlugin { /** * 获取已注册的 Agent namespace 列表 */ getRegisteredAgents: () => string[]; /** * 获取已注册 Agent 的 AgentCard 列表(替换 endpoint 为宿主地址) * * @param hostEndpoint 宿主服务器的 endpoint(Host Agent 的地址) * @returns AgentCard 列表,endpoint 已替换为 { host, port, namespace } */ getRegisteredAgentCards: (hostEndpoint: { host: string; port: number; }) => AgentCard[]; /** * 检查指定 namespace 是否已注册 */ isRegistered: (namespace: string) => boolean; /** * 获取指定 namespace 的原始 AgentCard(不替换 endpoint) */ getAgentCard: (namespace: string) => AgentCard | undefined; } /** * 创建 ParasiteHostPlugin */ declare const createParasiteHostPlugin: () => ParasiteHostPlugin; /** * ParasitePlugin - 寄生插件(Client 端) * * 生物学比喻: * - 本地 Agent(寄生者)主动连接到云端 Host Agent(宿主) * - 寄生者借助宿主的网络端点对外提供服务 * - 寄生者依赖宿主存活,宿主断开则寄生者失去连接 * * 核心功能: * 1. 连接到远程 Host Agent 并发送注册请求 * 2. 保持双向流连接,接收来自 Host Agent 的调用转发 * 3. 将调用路由到本地 Agent 的技能处理器 * * 使用场景: * - Tool Agent 使用此插件,寄生到云端 Host Agent * - 注册后 Tool Agent 成为 "一等公民",可被 Agent Selector 选择 * - Host Agent 的调用通过双向流透明转发给 Tool Agent * * 消息协议: * - agent-register: 发送注册请求(data: { agentCard: AgentCard }) * - agent-unregister: 发送注销请求(无需参数,Server 通过 stream 映射识别) * - call: 接收调用转发(从 Host Agent 转发) * - done/error/progress: 发送调用响应(返回给 Host Agent) * * @example * ```typescript * // 业务层决定 namespace * const namespace = `tool-agent@${userId}` * * const parasitePlugin = createParasitePlugin({ * address: 'a2a://host-agent.example.com:50050', * namespace, // 必传,由外部决定 * }) * * const server = createAgentServer(config) * .use(parasitePlugin) * * await server.start() * // Tool Agent 启动后自动寄生到 Host Agent * ``` */ /** * 事件回调配置 */ interface ParasiteEventCallbacks { /** * 连接断开时触发(与宿主断开) */ onDisconnect?: () => void; /** * 开始重连时触发 * @param attempt 当前重试次数 * @param delay 距离下次重试的延迟(毫秒) */ onReconnecting?: (attempt: number, delay: number) => void; /** * 重连成功时触发(重新寄生成功) */ onReconnected?: () => void; /** * 首次寄生成功时触发 */ onRegistered?: () => void; /** * 发生错误时触发 * @param error 错误对象 */ onError?: (error: Error) => void; } /** * ParasitePlugin 配置 */ interface ParasitePluginConfig { /** * 宿主地址(Host Agent 地址) * * 格式:a2a[s]://host:port * - a2a:// 表示无 TLS * - a2as:// 表示使用 TLS * * @example 'a2a://localhost:50050' * @example 'a2as://api.example.com:50050' */ address: string; /** * namespace(必传) * * 用于在宿主端标识此 Agent 的唯一路由键 * 由业务层决定,ParasitePlugin 不关心其格式和含义 * 例如:"tool-agent@user123"、UUID、sessionId 等 */ namespace: string; /** * 重连配置(可选) * * 用于控制连接失败或断开后的重试行为 */ reconnect?: ReconnectConfig; /** * 事件回调(可选) * * 用于监听连接状态变化,实现用户通知等功能 */ callbacks?: ParasiteEventCallbacks; } /** * ParasitePlugin 接口 */ interface ParasitePlugin extends ServerPlugin { /** * 检查是否已寄生到 Host Agent */ isRegistered: () => boolean; /** * 获取寄生状态 */ getRegistrationStatus: () => { registered: boolean; registeredAt?: number; /** 宿主地址(a2a:// 格式) */ hostAddress: string; }; /** * 断开连接并脱离宿主 */ detach: () => Promise<void>; } /** * 重连配置 */ interface ReconnectConfig { /** 最大重试次数(默认无限重试) */ maxRetries?: number; /** 基础延迟(毫秒,默认 2000) */ baseDelay?: number; /** 最大延迟(毫秒,默认 60000) */ maxDelay?: number; } /** * 创建 ParasitePlugin(寄生插件) * * 使本地 Agent 能够寄生到远程 Host Agent, * 借助宿主的网络端点对外提供服务。 */ declare const createParasitePlugin: (config: ParasitePluginConfig) => ParasitePlugin; /** * MCP Plugin 类型定义 * * 定义 MCP (Model Context Protocol) 集成所需的类型 */ /** * MCP Server 配置 - stdio 传输 */ interface MCPServerConfigStdio { /** Server 名称(用于生成 skill 前缀) */ name: string; /** 传输方式 */ transport: 'stdio'; /** 启动命令 */ command: string; /** 命令参数 */ args?: string[]; /** 环境变量 */ env?: Record<string, string>; /** 工作目录 */ cwd?: string; } /** * MCP Server 配置 - HTTP 传输 */ interface MCPServerConfigHttp { /** Server 名称(用于生成 skill 前缀) */ name: string; /** 传输方式 */ transport: 'http'; /** Server URL */ url: string; /** 请求头 */ headers?: Record<string, string>; } /** * MCP Server 配置(联合类型) */ type MCPServerConfig = MCPServerConfigStdio | MCPServerConfigHttp; /** * MCP Tool 定义(来自 MCP Server) */ interface MCPToolDefinition { /** 工具名称 */ name: string; /** 工具描述 */ description?: string; /** 输入参数 JSON Schema */ inputSchema: { type: 'object'; properties?: Record<string, any>; required?: string[]; }; } /** * MCP Tool 调用结果 */ interface MCPToolResult { /** 结果内容 */ content: Array<{ type: 'text' | 'image' | 'resource'; text?: string; data?: string; mimeType?: string; }>; /** 是否出错 */ isError?: boolean; } /** * MCP 连接状态 */ interface MCPConnectionState { /** Server 名称 */ serverName: string; /** 是否已连接 */ connected: boolean; /** 连接时间 */ connectedAt?: number; /** 可用工具数量 */ toolCount: number; /** 错误信息 */ error?: string; } /** * MCP Plugin 配置 */ interface MCPPluginConfig { /** MCP Server 列表 */ servers: MCPServerConfig[]; /** * Skill 名称前缀 * 默认: 'mcp' * 生成格式: {prefix}_{serverName}_{toolName} * 例如: mcp_github_create_issue */ skillPrefix?: string; /** * 连接超时(毫秒) * 默认: 30000 */ connectTimeout?: number; /** * 工具调用超时(毫秒) * 默认: 60000 */ callTimeout?: number; /** * 自动重连 * 默认: true */ autoReconnect?: boolean; /** * 重连间隔(毫秒) * 默认: 5000 */ reconnectInterval?: number; } /** * MCP Plugin 实例 */ interface MCPPlugin extends ServerPlugin { /** * 获取所有已连接的 MCP Server */ getConnectedServers: () => MCPConnectionState[]; /** * 获取所有 MCP Tools(已转换为 A2A Skill 格式) */ getMCPSkills: () => SkillDefinition[]; /** * 获取指定 Server 的工具列表 */ getServerTools: (serverName: string) => MCPToolDefinition[]; /** * 手动连接指定 Server */ connectServer: (serverName: string) => Promise<void>; /** * 断开指定 Server */ disconnectServer: (serverName: string) => Promise<void>; /** * 断开所有 Server */ disconnectAll: () => Promise<void>; /** * 检查 skill 是否为 MCP Tool */ isMCPSkill: (skillName: string) => boolean; } /** * MCP Plugin 实现 * * 将 MCP Tools 集成到 A2A Agent 中,作为 Skills 暴露给 LLM */ /** * 创建 MCP Plugin * * @example * ```typescript * const mcpPlugin = createMCPPlugin({ * servers: [ * { * name: 'github', * transport: 'stdio', * command: 'npx', * args: ['-y', '@modelcontextprotocol/server-github'], * env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN }, * }, * { * name: 'filesystem', * transport: 'stdio', * command: 'npx', * args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/dir'], * }, * ], * }) * * const server = createAgentServer(config) * .use(mcpPlugin) * * // MCP tools 自动合并到 AgentCard.skills * // 调用时自动路由到对应的 MCP Server * ``` */ declare const createMCPPlugin: (config: MCPPluginConfig) => MCPPlugin; /** * 通用 Tokenizer * * 使用简化算法计算文本的 Token 数量,与具体 LLM 无关 * * 算法规则: * - 中文字符:约 1 字 = 2 tokens * - 英文/数字:约 4 字符 = 1 token * - 空白字符:约 4 字符 = 1 token * - 其他符号:1 符号 = 1 token */ declare const tokenize: (text: string) => number; /** * 计算费用(分) * * @param tokens Token 数量 * @param pricePerKToken 每 1K tokens 的价格(分) * @returns 费用(分),向上取整 */ declare const calculateCost: (tokens: number, pricePerKToken: number) => number; /** * 流式响应收集器 * * 用于收集流式输出的所有文本,并计算总 Token 数 * * @example * ```typescript * const collector = createStreamCollector() * * // 收集流式输出 * collector.collect('Hello ') * collector.collect('World!') * * // 获取结果 * console.log(collector.getText()) // 'Hello World!' * console.log(collector.getTokens()) // Token 数量 * ``` */ declare const createStreamCollector: () => { /** * 收集一个文本块 */ collect: (chunk: string) => void; /** * 获取所有收集的文本 */ getText: () => string; /** * 获取总字符数 */ getChars: () => number; /** * 获取总 Token 数 */ getTokens: () => number; /** * 重置收集器 */ reset: () => void; }; type StreamCollector = ReturnType<typeof createStreamCollector>; /** * 创建 IO 承诺 * * 承诺包含内容哈希和 Token 数量,由签名密钥签名确保不可伪造 * userId 纳入签名保护,防止恶意 Agent 伪造用户身份 * * @param content 原始内容 * @param traceId 调用链 ID * @param type 类型(input/output) * @param signingKey 签名密钥 * @param userId 用户 ID(可选,来自上游传递的 x-user-id) * @returns IO 承诺 * * @example * ```typescript * const commitment = createIOCommitment( * JSON.stringify(params), * 'tr_abc123', * 'input', * 'signing-key', * 'user_123' * ) * ``` */ declare const createIOCommitment: (content: string, traceId: string, type: "input" | "output", signingKey: string, userId?: string) => IOCommitment; /** * 验证 IO 承诺 * * @param commitment 承诺数据 * @param type 类型(input/output) * @param signingKey 签名密钥 * @param userId 用户 ID(需与创建时一致) * @returns 验证是否通过 */ declare const verifyIOCommitment: (commitment: IOCommitment, type: "input" | "output", signingKey: string, userId?: string) => boolean; /** * 计算 SDK 核心模块的哈希 * * 用于验证 SDK 是否被篡改 * * @returns 核心模块的合并哈希 */ declare const calculateSdkHash: () => string; /** * 获取 SDK 版本 * * @returns SDK 版本号 */ declare const getSdkVersion: () => string; /** * Tracing Plugin 类型定义 * * 专注于调用链追踪,与 io-metrics-plugin 完全分离 */ /** * 调用链追踪记录 */ interface TraceRecord { /** 请求链 ID(整个请求链共用) */ traceId: string; /** 当前调用的唯一 ID */ spanId: string; /** 父调用的 Span ID(谁调用了我,根调用为 undefined) */ parentSpanId?: string; /** Agent ID */ agentId: string; /** 调用的技能名称 */ skill: string; /** 调用开始时间(ISO 8601 格式) */ startedAt: string; /** 执行时长(毫秒) */ duration: number; } /** * 调用链追踪提供者接口 * * 平台方实现此接口,接收调用链数据 */ interface TracingProvider { /** * 上报调用链追踪数据 * * @param record 追踪记录 */ reportTrace(record: TraceRecord): Promise<void>; } /** * Tracing Plugin 配置选项 */ interface TracingPluginOptions { /** 调用链追踪提供者 */ provider: TracingProvider; } /** * Tracing Plugin - 调用链追踪插件 * * 功能: * 1. beforeHandler: 生成 spanId,读取 parentSpanId,写入 x-span-id 供下游读取 * 2. afterHandler: 计算 duration,上报调用链数据到平台 * * 与 io-metrics-plugin 的区别: * - tracing-plugin: 只负责调用链追踪(spanId/parentSpanId) * - io-metrics-plugin: 只负责 Token 计量和计费 */ /** * 创建调用链追踪插件 * * @param options 插件配置 * @returns ServerPlugin * * @example * ```typescript * const server = createAgentServer(config) * .use(createTracingPlugin({ * provider: { * reportTrace: async (record) => { * await fetch(`${platformUrl}/api/traces/report`, { * method: 'POST', * headers: { 'Content-Type': 'application/json' }, * body: JSON.stringify(record), * }) * } * } * })) * ``` */ declare const createTracingPlugin: (options: TracingPluginOptions) => ServerPlugin; /** * Auth Plugin - 类型定义 */ /** * Token 验证结果 */ interface TokenVerifyResult { /** 是否有效 */ valid: boolean; /** 用户 ID(验证成功时存在) */ userId?: string; /** 用户名(验证成功时存在) */ username?: string; /** 错误信息(验证失败时存在) */ error?: string; } /** * Auth Plugin 配置选项 */ interface AuthPluginOptions { /** * 平台 URL(用于验证 Token) * * 如果未提供或为空字符串,当收到 authorization header 时会拒绝请求, * 因为无法安全地验证 Token。 * * @example 'http://8.153.165.230:50010/api' */ platformUrl?: string; /** * 是否强制要求登录 * * - true: 未登录用户会收到错误消息,请求被中止 * - false: 允许匿名用户访问 * * @default true */ requireAuth?: boolean; /** * 自定义错误消息 */ messages?: { /** Token 无效时的错误消息 */ tokenInvalid?: string; /** 需要登录时的错误消息 */ authRequired?: string; /** 验证服务不可用时的错误消息 */ serviceUnavailable?: string; /** 平台未配置时的错误消息(收到 auth header 但无法验证) */ platformNotConfigured?: string; }; } /** * Auth Plugin - 认证插件 * * 功能: * 1. beforeHandler: 从 gRPC metadata 提取 authorization header * 2. 调用平台 API 验证 Token * 3. 验证成功:将 userId/username 存入 ctx.metadata 供后续使用 * 4. 验证失败:发送错误消息并中止请求 * * 设计原则: * - 不信任客户端传递的 userId,必须通过平台验证 * - 验证后的 userId 通过 ctx.metadata 传递,而非 gRPC metadata * - 支持配置是否强制要求登录 */ /** * 创建认证插件 * * @param options - 插件配置 * @returns ServerPlugin * * @example * ```typescript * const server = createAgentServer(config) * .use(createA