@faapi/agent 3.2.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +119 -14
- package/dist/index.js +331 -98
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers/openai.ts","../src/provider.ts","../src/trace.ts","../src/reactLoop.ts","../src/agent.ts","../src/plugin.ts"],"sourcesContent":["import type { LlmConfig } from '@faapi/faapi';\nimport type {\n LLMCompleteRequest,\n LLMMessage,\n LLMProvider,\n LLMResponse,\n LLMStopReason,\n LLMStreamChunk,\n LLMToolCall,\n LLMToolDefinition,\n LLMUsage,\n} from '../provider';\n\n/**\n * OpenAI 兼容 API 的 LLMProvider 实现\n *\n * 通过 `LlmConfig.baseURL` 可指向任意 OpenAI 兼容中转服务\n * (官方 OpenAI / Azure OpenAI / LiteLLM / one-api / 自建网关)。\n *\n * 详见 [openai.md](./openai.md)。\n */\n\n/** OpenAI 默认 API 端点(未设 baseURL 时使用) */\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\n\n/**\n * OpenAI 已知保留字段集合(这些字段在 LlmConfig 中有专门处理,不算\"额外透传\")\n *\n * - `provider` / `apiKey` / `baseURL` —— LlmConfig 结构字段\n * - `model` —— 由 `request.model` 或 `config.models` 第一个 key 决定,不由 config 透传\n * - `models` —— 嵌套级联的 model 配置映射,非 OpenAI API 字段\n */\nconst RESERVED_CONFIG_KEYS = new Set(['provider', 'apiKey', 'model', 'baseURL', 'models']);\n\n/**\n * LLM Provider 错误\n *\n * 含 HTTP 状态码(网络错误为 `undefined`)和响应体摘要,\n * 业务方可通过 `instanceof LLMProviderError` 区分 LLM 错误与其他错误。\n */\nexport class LLMProviderError extends Error {\n /** HTTP 状态码(网络错误 / JSON 解析错误为 undefined) */\n readonly status?: number;\n /** 响应体摘要(前 500 字符,便于诊断) */\n readonly body?: string;\n\n constructor(message: string, options?: { status?: number; body?: string; cause?: unknown }) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = 'LLMProviderError';\n this.status = options?.status;\n this.body = options?.body;\n }\n}\n\n/** 内部存储:OpenAI chat completions 请求体(构造阶段) */\ninterface OpenAIRequestBody {\n model?: string;\n messages: unknown[];\n tools?: unknown[];\n stream?: boolean;\n temperature?: number;\n max_tokens?: number;\n [key: string]: unknown;\n}\n\n/** 内部存储:OpenAI tool_call 项(非流式 + 流式统一,index 仅流式有) */\ninterface OpenAIToolCall {\n index?: number;\n id?: string;\n type?: string;\n function?: { name?: string; arguments?: string };\n}\n\n/** 内部存储:OpenAI usage 字段 */\ninterface OpenAIUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}\n\n/** 内部存储:OpenAI chat completions 响应 */\ninterface OpenAIResponseJson {\n choices?: Array<{\n index?: number;\n message?: {\n role?: string;\n content?: string | null;\n tool_calls?: OpenAIToolCall[];\n };\n finish_reason?: string | null;\n }>;\n usage?: OpenAIUsage;\n}\n\n/** 内部存储:OpenAI streaming chunk */\ninterface OpenAIStreamChunkJson {\n choices?: Array<{\n delta?: {\n content?: string | null;\n tool_calls?: OpenAIToolCall[];\n };\n finish_reason?: string | null;\n }>;\n usage?: OpenAIUsage;\n}\n\n/** tool call 累积器(stream 模式按 index 累积 args 字符串) */\ninterface ToolCallAccumulator {\n id?: string;\n name?: string;\n argsString: string;\n}\n\n/**\n * 创建 OpenAI 兼容 LLMProvider\n *\n * @param config LLM 提供方配置(apiKey / baseURL / models + provider 级透传字段,\n * model 级字段在 `models[modelName]` 下覆盖 provider 级同名字段)\n * @returns LLMProvider 实例(含 complete + stream 方法)\n */\nexport function createOpenAIProvider(config: LlmConfig): LLMProvider {\n const baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const apiKey = config.apiKey;\n\n /**\n * 构造 OpenAI chat completions 请求体\n *\n * 字段优先级(高 → 低):\n * 1. `request.temperature` / `request.maxTokens` —— request 级覆盖\n * 2. `config.models[modelName][key]` —— model 级覆盖(如 model 级 temperature)\n * 3. `config[key]` —— provider 级透传(如 provider 级 temperature)\n *\n * `model` 由 `request.model` 或 `config.models` 第一个 key 决定,\n * 不从 config 透传(避免 config 级 `model` 字段覆盖已解析的 model 名)。\n */\n function buildRequestBody(request: LLMCompleteRequest): OpenAIRequestBody {\n // 确定 model 名:request 优先,否则取 config.models 第一个 key\n const modelName = request.model ?? Object.keys(config.models)[0];\n const modelConfig = modelName ? config.models[modelName] : undefined;\n\n const body: OpenAIRequestBody = {\n model: modelName,\n messages: request.messages.map(toOpenAIMessage),\n };\n\n if (request.tools && request.tools.length > 0) {\n body.tools = request.tools.map(toOpenAITool);\n }\n\n // 合并 config 透传字段:provider 级 + model 级覆盖(model 级优先)\n // 跳过 RESERVED_CONFIG_KEYS(结构字段 + model 名 + models 嵌套配置)\n const mergedConfig: Record<string, unknown> = {};\n for (const key of Object.keys(config)) {\n if (RESERVED_CONFIG_KEYS.has(key)) continue;\n const value = (config as Record<string, unknown>)[key];\n if (value !== undefined) {\n mergedConfig[key] = value;\n }\n }\n if (modelConfig) {\n for (const key of Object.keys(modelConfig)) {\n if (RESERVED_CONFIG_KEYS.has(key)) continue;\n const value = (modelConfig as Record<string, unknown>)[key];\n if (value !== undefined) {\n mergedConfig[key] = value; // 覆盖 provider 级同名字段\n }\n }\n }\n\n // 应用透传字段到 body(跳过已设置的结构字段 model/messages/tools)\n for (const key of Object.keys(mergedConfig)) {\n const value = mergedConfig[key];\n if (value !== undefined && !(key in body)) {\n body[key] = value;\n }\n }\n\n // request 级覆盖(最高优先级)\n if (request.temperature !== undefined) body.temperature = request.temperature;\n if (request.maxTokens !== undefined) body.max_tokens = request.maxTokens;\n\n return body;\n }\n\n /** 构造请求 headers */\n function buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (apiKey) headers.Authorization = `Bearer ${apiKey}`;\n return headers;\n }\n\n /** 调 fetch,统一捕获网络错误 */\n async function safeFetch(url: string, init: RequestInit): Promise<Response> {\n try {\n return await fetch(url, init);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new LLMProviderError(`Network error: ${reason}`, { cause: err });\n }\n }\n\n /** 校验 HTTP 响应状态,非 2xx 抛 LLMProviderError */\n async function ensureOk(response: Response): Promise<string> {\n if (response.ok) return '';\n const bodyText = await response.text();\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError(`HTTP ${response.status}: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n // ─── complete ──────────────────────────────────────\n\n async function complete(request: LLMCompleteRequest): Promise<LLMResponse> {\n const url = `${baseURL}/chat/completions`;\n const body = buildRequestBody(request);\n const init: RequestInit = {\n method: 'POST',\n headers: buildHeaders(),\n body: JSON.stringify(body),\n };\n\n const response = await safeFetch(url, init);\n await ensureOk(response);\n\n const bodyText = await response.text();\n let json: OpenAIResponseJson;\n try {\n json = JSON.parse(bodyText) as OpenAIResponseJson;\n } catch {\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError(`Invalid JSON response: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n const choice = json.choices?.[0];\n const msg = choice?.message;\n if (!choice || !msg) {\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError('Empty choices in response', {\n status: response.status,\n body: excerpt,\n });\n }\n\n const content = msg.content ?? '';\n const toolCalls = parseToolCalls(msg.tool_calls, bodyText, response.status);\n\n return {\n message: {\n role: 'assistant',\n content,\n toolCalls,\n },\n stopReason: mapStopReason(choice.finish_reason ?? undefined),\n usage: mapUsage(json.usage),\n };\n }\n\n // ─── stream ──────────────────────────────────────\n\n async function* stream(request: LLMCompleteRequest): AsyncIterable<LLMStreamChunk> {\n const url = `${baseURL}/chat/completions`;\n const body = buildRequestBody(request);\n body.stream = true;\n\n const init: RequestInit = {\n method: 'POST',\n headers: buildHeaders(),\n body: JSON.stringify(body),\n };\n\n const response = await safeFetch(url, init);\n await ensureOk(response);\n\n if (!response.body) {\n throw new LLMProviderError('Response body is null (streaming unsupported)', {\n status: response.status,\n });\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n /** tool call 累积器,按 index 聚合 args 字符串 */\n const accumulators = new Map<number, ToolCallAccumulator>();\n let finishReason: LLMStopReason | undefined;\n let usage: LLMUsage | undefined;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // SSE 事件以 \\n\\n 分隔\n let sep: number;\n while ((sep = buffer.indexOf('\\n\\n')) >= 0) {\n const eventStr = buffer.slice(0, sep);\n buffer = buffer.slice(sep + 2);\n\n const data = extractSSEData(eventStr);\n if (data === null) continue; // 注释行 / 心跳行 / 非 data 行\n\n if (data === '[DONE]') {\n // emit 最终 chunk(含累积的 toolCalls + finishReason + usage)\n yield finalizeStreamChunk(accumulators, finishReason, usage);\n return;\n }\n\n let chunk: OpenAIStreamChunkJson;\n try {\n chunk = JSON.parse(data) as OpenAIStreamChunkJson;\n } catch {\n const excerpt = data.slice(0, 500);\n throw new LLMProviderError(`Invalid SSE chunk: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n const delta = chunk.choices?.[0]?.delta;\n if (delta) {\n // 内容增量\n if (typeof delta.content === 'string' && delta.content.length > 0) {\n yield { deltaContent: delta.content };\n }\n // tool_calls 增量累积\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n accumulateToolCall(accumulators, tc);\n }\n }\n }\n\n const fr = chunk.choices?.[0]?.finish_reason;\n if (fr) finishReason = mapStopReason(fr);\n if (chunk.usage) usage = mapUsage(chunk.usage);\n }\n }\n\n // 流自然结束(无 [DONE]),emit 最终 chunk\n yield finalizeStreamChunk(accumulators, finishReason, usage);\n } finally {\n reader.releaseLock();\n }\n }\n\n return { complete, stream };\n}\n\n// ─── 辅助函数 ──────────────────────────────────────\n\n/** LLMMessage → OpenAI 消息格式 */\nfunction toOpenAIMessage(msg: LLMMessage): unknown {\n const out: Record<string, unknown> = {\n role: msg.role,\n content: msg.content,\n };\n if (msg.toolCallId !== undefined) out.tool_call_id = msg.toolCallId;\n if (msg.toolCalls !== undefined && msg.toolCalls.length > 0) {\n out.tool_calls = msg.toolCalls.map((tc) => ({\n id: tc.id,\n type: 'function',\n function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },\n }));\n }\n return out;\n}\n\n/** LLMToolDefinition → OpenAI tool 定义格式 */\nfunction toOpenAITool(tool: LLMToolDefinition): unknown {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.input,\n },\n };\n}\n\n/** OpenAI tool_calls 数组 → LLMToolCall[],JSON.parse 每个 arguments */\nfunction parseToolCalls(\n toolCalls: OpenAIToolCall[] | undefined,\n bodyText: string,\n status: number,\n): LLMToolCall[] | undefined {\n if (!toolCalls || toolCalls.length === 0) return undefined;\n\n const result: LLMToolCall[] = [];\n for (let i = 0; i < toolCalls.length; i++) {\n const tc = toolCalls[i];\n const argsStr = tc?.function?.arguments ?? '{}';\n let args: Record<string, unknown>;\n try {\n args = JSON.parse(argsStr);\n } catch {\n const excerpt = argsStr.slice(0, 500);\n throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {\n status,\n body: bodyText.slice(0, 500),\n });\n }\n result.push({\n id: tc?.id ?? `call_${i}`,\n name: tc?.function?.name ?? '',\n arguments: args,\n });\n }\n return result;\n}\n\n/** OpenAI finish_reason → LLMStopReason */\nfunction mapStopReason(fr: string | undefined | null): LLMStopReason {\n switch (fr) {\n case 'stop':\n return 'stop';\n case 'tool_calls':\n return 'tool_calls';\n case 'length':\n return 'length';\n case 'content_filter':\n return 'content_filter';\n default:\n return 'other';\n }\n}\n\n/** OpenAI usage → LLMUsage */\nfunction mapUsage(u?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}): LLMUsage | undefined {\n if (!u) return undefined;\n return {\n promptTokens: u.prompt_tokens ?? 0,\n completionTokens: u.completion_tokens ?? 0,\n totalTokens: u.total_tokens ?? 0,\n };\n}\n\n/** 从 SSE 事件块提取 data: 行内容(多行用 \\n 拼接) */\nfunction extractSSEData(event: string): string | null {\n const dataLines: string[] = [];\n for (const line of event.split('\\n')) {\n // 跳过空行 / 注释行 / 心跳行\n if (line === '' || line.startsWith(':')) continue;\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n // 其他字段(event: / id: / retry:)忽略,OpenAI 不用\n }\n if (dataLines.length === 0) return null;\n return dataLines.join('\\n');\n}\n\n/** 累积 tool call chunk 到按 index 的 Map */\nfunction accumulateToolCall(\n accumulators: Map<number, ToolCallAccumulator>,\n tc: OpenAIToolCall,\n): void {\n const idx = tc.index ?? 0;\n const acc = accumulators.get(idx) ?? { argsString: '' };\n if (tc.id) acc.id = tc.id;\n if (tc.function?.name) acc.name = tc.function.name;\n if (tc.function?.arguments) acc.argsString += tc.function.arguments;\n accumulators.set(idx, acc);\n}\n\n/** 流结束时把累积器转为 LLMToolCall[] + finishReason + usage 一并 emit */\nfunction finalizeStreamChunk(\n accumulators: Map<number, ToolCallAccumulator>,\n finishReason: LLMStopReason | undefined,\n usage: LLMUsage | undefined,\n): LLMStreamChunk {\n const toolCalls: LLMToolCall[] = [];\n if (accumulators.size > 0) {\n const indices = Array.from(accumulators.keys()).sort((a, b) => a - b);\n for (const idx of indices) {\n const acc = accumulators.get(idx)!;\n // 跳过不完整(无 id 或 name)的累积,异常流不应阻止结束\n if (!acc.id || !acc.name) continue;\n let args: Record<string, unknown>;\n try {\n args = acc.argsString ? JSON.parse(acc.argsString) : {};\n } catch {\n const excerpt = acc.argsString.slice(0, 500);\n throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {\n body: excerpt,\n });\n }\n toolCalls.push({\n id: acc.id,\n name: acc.name,\n arguments: args,\n });\n }\n }\n\n const chunk: LLMStreamChunk = {};\n if (toolCalls.length > 0) chunk.toolCalls = toolCalls;\n if (finishReason) chunk.finishReason = finishReason;\n if (usage) chunk.usage = usage;\n return chunk;\n}\n","import type { LlmConfig } from '@faapi/faapi';\nimport { createOpenAIProvider } from './providers/openai';\n\n/**\n * LLM Provider 抽象层\n *\n * 统一 `complete` / `stream` 接口,屏蔽 OpenAI / Anthropic 等 LLM 服务差异。\n * [reactLoop](./reactLoop.md) 与 [Agent 类](./agent.md) 通过此接口调用 LLM,\n * 与具体 provider 解耦。\n *\n * 详见 [provider.md](./provider.md)。\n */\n\n/**\n * 对话消息\n *\n * 四种 role 与 OpenAI chat completions 一致:\n * - `system` —— 系统提示词(agent 的 systemPrompt)\n * - `user` —— 用户输入\n * - `assistant` —— LLM 回复(可能含 toolCalls)\n * - `tool` —— tool 执行结果(需带 toolCallId 标识对应哪个 tool_call)\n */\nexport interface LLMMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n /** 消息内容(assistant 角色 + tool_calls 时可能为空字符串) */\n content: string;\n /** role='tool' 时:对应的 tool_call ID(用于 LLM 关联 tool 结果) */\n toolCallId?: string;\n /** role='assistant' 时:LLM 请求的 tool 调用(reactLoop 据此执行 tool) */\n toolCalls?: LLMToolCall[];\n}\n\n/**\n * LLM 请求的 tool 调用\n *\n * 由 LLM 在 assistant 消息中返回。`arguments` 已 JSON.parse,\n * reactLoop 直接传给 tool 函数。\n */\nexport interface LLMToolCall {\n /** tool call ID(provider 分配,用于匹配 tool 结果) */\n id: string;\n /** tool 名(匹配 LLMToolDefinition.name) */\n name: string;\n /** tool 参数(已 JSON.parse 的对象) */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool 定义\n *\n * 由 [reactLoop](./reactLoop.md) 从 [toolRegistry](../../faapi/src/injection/toolRegistry.md)\n * + [agentRegistry.resolveSubAgents](../../faapi/src/injection/agentRegistry.md) 组装:\n * - 常规 tool:`input` 来自 AST 提取的 zod schema(JSON Schema 形式)\n * - agent-as-tool:`input` 为自由 schema(agent 参数开放)\n */\nexport interface LLMToolDefinition {\n /** tool 名(如 `weather.getWeather` 或 `agent.researcher`) */\n name: string;\n /** tool 描述(对 LLM 可见,引导 LLM 选择调用) */\n description?: string;\n /** JSON Schema 对象(描述 tool 参数结构) */\n input: Record<string, unknown>;\n}\n\n/**\n * complete / stream 的入参\n *\n * `model` / `temperature` / `maxTokens` 优先级高于 [LlmConfig](../../faapi/src/config/configTypes.md)\n * (agent 自身 `config.model` 覆盖全局默认)。\n */\nexport interface LLMCompleteRequest {\n /** 对话消息(含历史 + 当前轮) */\n messages: LLMMessage[];\n /** 可用 tool 列表(未提供时 LLM 不会发起 tool_call) */\n tools?: LLMToolDefinition[];\n /** 覆盖 LlmConfig.model(agent 自身配置优先) */\n model?: string;\n /** 采样温度(0~2) */\n temperature?: number;\n /** 最大生成 token 数 */\n maxTokens?: number;\n}\n\n/**\n * LLM 响应的停止原因\n *\n * - `stop` —— LLM 主动结束(自然结束 / 遇到 stop 序列)\n * - `tool_calls` —— LLM 请求调用 tool(reactLoop 据此进入下一轮)\n * - `length` —— 达到 max_tokens 上限\n * - `content_filter` —— 内容过滤触发\n * - `other` —— 其他原因(未识别的 finish_reason)\n */\nexport type LLMStopReason = 'stop' | 'tool_calls' | 'length' | 'content_filter' | 'other';\n\n/**\n * complete 的返回\n *\n * `message.toolCalls` 不为空时 stopReason 应为 `'tool_calls'`。\n */\nexport interface LLMResponse {\n /** assistant 消息(含 content + 可选 toolCalls) */\n message: LLMMessage;\n /** 停止原因(reactLoop 据此判断是否进入下一轮) */\n stopReason: LLMStopReason;\n /** token 用量(部分 provider 不返回) */\n usage?: LLMUsage;\n}\n\n/**\n * Token 用量\n */\nexport interface LLMUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n}\n\n/**\n * stream 的单个 chunk\n *\n * - 内容流:`deltaContent` 为增量 token\n * - tool 调用:累积完成后在最终 chunk 一并 emit `toolCalls`\n * - 结束:最终 chunk 含 `finishReason` + 可选 `usage`\n */\nexport interface LLMStreamChunk {\n /** 增量内容(streaming token) */\n deltaContent?: string;\n /** 累积完成的 tool 调用(在最终 chunk 出现) */\n toolCalls?: LLMToolCall[];\n /** 结束原因(只在最终 chunk 出现) */\n finishReason?: LLMStopReason;\n /** token 用量(部分 provider 在最终 chunk 提供) */\n usage?: LLMUsage;\n}\n\n/**\n * LLM Provider 抽象接口\n *\n * 实现方:\n * - [createOpenAIProvider](./providers/openai.md) —— OpenAI 兼容 API\n *\n * 消费方:\n * - [reactLoop](./reactLoop.md) Phase 3.3 —— 调 complete / stream 执行 ReAct 循环\n * - [Agent 类](./agent.md) Phase 3.4 —— 构造时持有 provider 实例\n */\nexport interface LLMProvider {\n /** 非流式:阻塞到 LLM 返回完整响应 */\n complete(request: LLMCompleteRequest): Promise<LLMResponse>;\n /** 流式:异步迭代 chunk(增量 token + tool call + 结束) */\n stream(request: LLMCompleteRequest): AsyncIterable<LLMStreamChunk>;\n}\n\n/**\n * LLM Provider 错误\n *\n * 由具体 provider 抛出,包含 HTTP 状态码(网络错误为 `undefined`)和响应体摘要。\n * 业务方可通过 `instanceof LLMProviderError` 区分 LLM 错误与其他错误。\n *\n * 重新从 [./providers/openai](./providers/openai.md) 导出,便于业务方从此模块统一捕获。\n */\nexport { LLMProviderError } from './providers/openai';\n\n/**\n * 按 `config.provider` 路由到对应的 LLM 适配器\n *\n * Phase 3.2 仅支持 `'openai'`,其他值抛错不静默降级(参考 AGENTS.md §6.3)。\n *\n * @param config LLM 提供方配置(来自 faapi.config.ts 的 `agent.llms[key]`)\n * @returns LLMProvider 实例\n * @throws {Error} 当 `config.provider` 不是已支持的值\n */\nexport function createProvider(config: LlmConfig): LLMProvider {\n switch (config.provider) {\n case 'openai':\n return createOpenAIProvider(config);\n default:\n throw new Error(`Unsupported LLM provider: ${String(config.provider)}`);\n }\n}\n","import type { LLMMessage, LLMStopReason, LLMUsage } from './provider';\n\n/**\n * 单次 agent.run() / agent.stream() 的结构化调用明细\n *\n * 默认开启(`enableTracing` 默认 `true`),业务方在生产主路径显式\n * `enableTracing: false` 关闭以零开销运行。详见 [trace.md](./trace.md)。\n */\nexport interface AgentTrace {\n /** agent 名 */\n agentName: string;\n /** 开始时间(performance.now() ms,相对进程启动,便于算相对耗时) */\n startedAt: number;\n /** 总耗时 ms(done 时填充,抛错时也填充) */\n durationMs?: number;\n /** 总轮数(与 ReactLoopResult.turns 一致) */\n turns: number;\n /** 累计 token 用量(与 ReactLoopResult.usage 一致) */\n usage?: LLMUsage;\n /** 最终停止原因(与 ReactLoopResult.stopReason 一致) */\n stopReason?: LLMStopReason;\n /** 最终 assistant 内容(与 ReactLoopResult.content 一致) */\n content?: string;\n /** agent 抛错时的错误消息(agent.run 抛错时填充) */\n error?: string;\n /** 按发生顺序的事件列表 */\n events: AgentTraceEvent[];\n}\n\n/**\n * trace 事件(discriminated union,按 `type` 区分)\n *\n * 一次 agent.run() 内的事件序列示例:\n * llm_call(turn=1) → tool_call(turn=1) → llm_call(turn=2) → done\n *\n * sub-agent 调用作为 `subagent_call` 事件,内嵌 sub-trace(递归结构)。\n */\nexport type AgentTraceEvent = LlmCallEvent | ToolCallEvent | SubAgentCallEvent;\n\n/**\n * LLM 调用事件——每轮调 provider.complete() / provider.stream() 触发一次\n */\nexport interface LlmCallEvent {\n type: 'llm_call';\n /** 第几轮(从 1 开始) */\n turn: number;\n startedAt: number;\n durationMs?: number;\n /** 该轮调用的 model 名 */\n model: string;\n /** 该轮发给 LLM 的输入消息快照(浅拷贝数组,消息对象引用共享) */\n inputMessages: LLMMessage[];\n /** 该轮 LLM 返回的 assistant 消息(含 toolCalls 若有) */\n response: LLMMessage;\n /** 该轮的停止原因 */\n stopReason: LLMStopReason;\n /** 该轮的 token 用量(provider 不返回时 undefined) */\n usage?: LLMUsage;\n}\n\n/**\n * 常规 tool 调用事件——executeTool 返回 unknown 时触发\n */\nexport interface ToolCallEvent {\n type: 'tool_call';\n turn: number;\n startedAt: number;\n durationMs?: number;\n /** LLM 分配的 tool_call_id(与 messages 里 tool 消息的 toolCallId 一致) */\n toolCallId: string;\n /** tool 名 */\n name: string;\n /** tool 参数(已 JSON.parse 的对象) */\n arguments: Record<string, unknown>;\n /** tool 执行结果(stringifyResult 后的字符串,与 messages 里 tool 消息一致) */\n result: string;\n /** tool 抛错时填充(result 为 stringifyError 后的字符串) */\n error?: string;\n}\n\n/**\n * sub-agent 调用事件——executeTool 返回 TracingToolResult 时触发\n *\n * sub-agent 的 trace 嵌入 `trace` 字段(递归结构),业务方可还原完整调用树。\n */\nexport interface SubAgentCallEvent {\n type: 'subagent_call';\n turn: number;\n startedAt: number;\n durationMs?: number;\n toolCallId: string;\n /** 被调用的 sub-agent 名 */\n agentName: string;\n /** 喂给 sub-agent 的输入(args JSON.stringify 后) */\n input: string;\n /** sub-agent 自己的 trace(递归) */\n trace: AgentTrace;\n /** sub-agent 返回的最终内容(sub-trace.content 的副本,便于不展开 sub-trace 也能读到结果) */\n result?: string;\n /** sub-agent 抛错时填充 */\n error?: string;\n}\n\n/**\n * sub-agent 调用的特殊返回值——reactLoop 据此识别 sub-agent 调用并发出 subagent_call 事件\n *\n * [Agent.executeSubAgent](./agent.md) 在 `enableTracing=true` 时返回此类型;\n * `enableTracing=false` 时返回 `unknown`(与常规 tool 一致)。\n *\n * `__trace` 是标记字段,避免与普通对象返回值冲突。reactLoop 通过\n * `typeof result === 'object' && result !== null && result.__trace === true`\n * 判断是否为 TracingToolResult。\n */\nexport interface TracingToolResult {\n /** 标记字段(避免与普通对象返回值冲突) */\n __trace: true;\n /** sub-agent 返回的业务结果(stringifyResult 后会作为 tool 消息内容) */\n result: unknown;\n /** sub-agent 自己的 trace */\n trace: AgentTrace;\n}\n\n/**\n * 类型守卫:判断 ToolExecutor 返回值是否为 TracingToolResult\n *\n * reactLoop 用此函数区分 sub-agent 调用(发出 subagent_call 事件)\n * 与常规 tool 调用(发出 tool_call 事件)。\n */\nexport function isTracingToolResult(value: unknown): value is TracingToolResult {\n return (\n typeof value === 'object' && value !== null && (value as { __trace?: unknown }).__trace === true\n );\n}\n","import type {\n LLMMessage,\n LLMProvider,\n LLMStopReason,\n LLMToolCall,\n LLMToolDefinition,\n LLMUsage,\n} from './provider';\nimport {\n isTracingToolResult,\n type AgentTrace,\n type AgentTraceEvent,\n type TracingToolResult,\n} from './trace';\n\n/**\n * ReAct(Reasoning + Acting)循环引擎\n *\n * 反复调 LLM、执行 tool、把结果回传 LLM,直到 LLM 给出最终回答或达到 `maxTurns` 上限。\n *\n * 详见 [reactLoop.md](./reactLoop.md)。\n */\n\n/**\n * Tool 执行函数\n *\n * 由 [Agent 类](./agent.md)提供——reactLoop 不关心 tool 如何被找到和执行。\n * Agent 类的 `executeTool` 实现:\n * - 常规 tool → `loadToolModule` 加载 handler 并调用\n * - agent-as-tool(`agent.` 前缀)→ 递归调子 agent 的 reactLoop(含 `maxAgentDepth` 防护)\n *\n * 返回值可以是任意类型——非 string 会被 JSON.stringify 后回传 LLM。\n *\n * sub-agent 调用时,若 `enableTracing=true`,返回 [TracingToolResult](./trace.md)\n * 携带 sub-agent 的 trace,reactLoop 据此发出 `subagent_call` 事件(嵌套递归 trace)。\n */\nexport type ToolExecutor = (\n name: string,\n args: Record<string, unknown>,\n) => Promise<unknown | TracingToolResult>;\n\n/**\n * reactLoop 配置\n *\n * 由 [Agent 类](./agent.md)组装并传入。\n */\nexport interface ReactLoopConfig {\n /** LLM provider 实例(由 [createProvider](./provider.md) 创建) */\n provider: LLMProvider;\n /** 系统提示词(来自 agent metadata 的 `systemPrompt`) */\n systemPrompt?: string;\n /** 可用 tool 列表(由 `resolveAgentTools` + `resolveSubAgents().map(asTool)` 组装) */\n tools?: LLMToolDefinition[];\n /** tool 执行函数(由 Agent 类提供,路由到常规 tool 或子 agent) */\n executeTool: ToolExecutor;\n /** 最大对话轮数(默认 10,来自 [AgentConfig](../../faapi/src/config/configTypes.md).maxTurns) */\n maxTurns?: number;\n /** 覆盖 LLM 模型名(来自 agent metadata 的 `model`) */\n model?: string;\n /** 采样温度 */\n temperature?: number;\n /** 最大生成 token 数 */\n maxTokens?: number;\n /**\n * 启用 tracing(默认 true)。开启时填充 `ReactLoopResult.trace` /\n * `ReactLoopStreamChunk.traceEvent`,详见 [trace.md](./trace.md)。\n *\n * 业务方在生产主路径显式传 `false` 关闭以零开销运行。\n */\n enableTracing?: boolean;\n}\n\n/**\n * 非流式循环结果\n */\nexport interface ReactLoopResult {\n /** 最终 assistant 消息内容 */\n content: string;\n /** 完整对话历史(system + user + assistant + tool 消息) */\n messages: LLMMessage[];\n /** 使用的轮数(含最终轮) */\n turns: number;\n /** 最终轮的停止原因 */\n stopReason: LLMStopReason;\n /** 累计 token 用量(多轮累加,provider 不返回时为 `undefined`) */\n usage?: LLMUsage;\n /**\n * 结构化调用明细(`enableTracing=true` 时填充,否则 `undefined` 零开销)。\n * 详见 [trace.md](./trace.md)。\n */\n trace?: AgentTrace;\n}\n\n/**\n * 流式循环的单个 chunk\n *\n * 每个 chunk 至多含一个字段:\n * - `deltaContent` — LLM 增量 token(多次 yield)\n * - `toolCall` — tool 开始执行\n * - `toolResult` — tool 执行完成\n * - `traceEvent` — trace 事件(`enableTracing=true` 时增量推送,与上述字段互斥)\n * - `done` — 循环结束(只 yield 一次)\n */\nexport interface ReactLoopStreamChunk {\n /** LLM 增量 token */\n deltaContent?: string;\n /** tool 开始执行(LLM 请求调用 tool) */\n toolCall?: { name: string; arguments: Record<string, unknown> };\n /** tool 执行完成(含结果) */\n toolResult?: { name: string; result: string };\n /**\n * trace 事件(`enableTracing=true` 时增量推送)。\n * 与 deltaContent / toolCall / toolResult / done 互斥,一个 chunk 至多一个字段。\n */\n traceEvent?: AgentTraceEvent;\n /** 循环结束 */\n done?: {\n content: string;\n turns: number;\n stopReason: LLMStopReason;\n usage?: LLMUsage;\n };\n}\n\n/**\n * reactLoop 系统级错误\n *\n * 目前仅用于 `maxTurns` 超限。tool 执行错误不抛此类型——它们被 catch 后回传 LLM。\n */\nexport class ReactLoopError extends Error {\n /** 配置的 maxTurns 值 */\n readonly maxTurns: number;\n\n constructor(message: string, maxTurns: number) {\n super(message);\n this.name = 'ReactLoopError';\n this.maxTurns = maxTurns;\n }\n}\n\n/** 默认最大轮数 */\nconst DEFAULT_MAX_TURNS = 10;\n\n/** 把 tool 执行结果转为字符串(非 string 自动 JSON.stringify) */\nfunction stringifyResult(result: unknown): string {\n if (typeof result === 'string') return result;\n if (result === undefined) return '';\n return JSON.stringify(result);\n}\n\n/** 把 tool 执行错误转为字符串 */\nfunction stringifyError(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\n/** 累加 usage */\nfunction accumulateUsage(a: LLMUsage | undefined, b: LLMUsage): LLMUsage {\n if (!a) return { ...b };\n return {\n promptTokens: a.promptTokens + b.promptTokens,\n completionTokens: a.completionTokens + b.completionTokens,\n totalTokens: a.totalTokens + b.totalTokens,\n };\n}\n\n/** 构造初始 messages(可选 system + user input) */\nfunction buildInitialMessages(input: string, systemPrompt?: string): LLMMessage[] {\n const messages: LLMMessage[] = [];\n if (systemPrompt) {\n messages.push({ role: 'system', content: systemPrompt });\n }\n messages.push({ role: 'user', content: input });\n return messages;\n}\n\n/** 构造 LLM complete/stream 请求参数(除 messages 外的公共字段) */\nfunction buildRequestExtras(config: ReactLoopConfig) {\n return {\n tools: config.tools,\n model: config.model,\n temperature: config.temperature,\n maxTokens: config.maxTokens,\n };\n}\n\n/** 当前时间戳(performance.now() ms,相对进程启动) */\nfunction nowMs(): number {\n return performance.now();\n}\n\n/**\n * 从 sub-agent tool 名提取 agent 名\n *\n * sub-agent tool 命名约定:`agent.<agentName>`(见 [agentRegistry.asTool](../../faapi/src/injection/agentRegistry.md))。\n * 非 `agent.` 前缀的原样返回(用于业务方自定义 sub-agent tool 命名)。\n */\nfunction extractSubAgentName(toolName: string): string {\n const prefix = 'agent.';\n if (toolName.startsWith(prefix)) {\n return toolName.slice(prefix.length);\n }\n return toolName;\n}\n\n// ─── reactLoop(非流式)──────────────────────────────\n\n/**\n * 执行 ReAct 循环(非流式)\n *\n * 反复调 `provider.complete()` → 执行 tool → 回传结果,直到 LLM 返回 `stop`(或其他非 `tool_calls` 原因)或超出 `maxTurns`。\n *\n * @param input 用户输入\n * @param config 循环配置\n * @returns 最终结果(content + messages + turns + stopReason + usage)\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.complete 抛错时立即传播\n */\nexport async function reactLoop(input: string, config: ReactLoopConfig): Promise<ReactLoopResult> {\n const enableTracing = config.enableTracing ?? true;\n const messages = buildInitialMessages(input, config.systemPrompt);\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n const extras = buildRequestExtras(config);\n let totalUsage: LLMUsage | undefined;\n let turns = 0;\n\n // trace 采集容器(enableTracing=false 时不构造,零开销)\n const traceStartedAt = enableTracing ? nowMs() : 0;\n const traceEvents: AgentTraceEvent[] | undefined = enableTracing ? [] : undefined;\n\n while (turns < maxTurns) {\n turns++;\n\n const llmStartedAt = enableTracing ? nowMs() : 0;\n // 浅拷贝快照:该轮发给 LLM 的输入消息(数组新对象,消息对象引用共享)\n const inputSnapshot = enableTracing ? [...messages] : undefined;\n const response = await config.provider.complete({\n messages: [...messages],\n ...extras,\n });\n\n if (response.usage) {\n totalUsage = accumulateUsage(totalUsage, response.usage);\n }\n\n if (enableTracing) {\n const llmEndedAt = nowMs();\n traceEvents!.push({\n type: 'llm_call',\n turn: turns,\n startedAt: llmStartedAt,\n durationMs: llmEndedAt - llmStartedAt,\n model: config.model ?? '',\n inputMessages: inputSnapshot!,\n response: response.message,\n stopReason: response.stopReason,\n usage: response.usage,\n });\n }\n\n // 把 assistant 消息加入历史\n messages.push(response.message);\n\n // 非 tool_calls → 循环结束\n if (response.stopReason !== 'tool_calls' || !response.message.toolCalls) {\n const traceEndedAt = enableTracing ? nowMs() : 0;\n return {\n content: response.message.content,\n messages,\n turns,\n stopReason: response.stopReason,\n usage: totalUsage,\n trace: enableTracing\n ? {\n agentName: '',\n startedAt: traceStartedAt,\n durationMs: traceEndedAt - traceStartedAt,\n turns,\n usage: totalUsage,\n stopReason: response.stopReason,\n content: response.message.content,\n events: traceEvents!,\n }\n : undefined,\n };\n }\n\n // 执行每个 tool call\n for (const toolCall of response.message.toolCalls) {\n const toolStartedAt = enableTracing ? nowMs() : 0;\n let resultStr: string;\n let rawResult: unknown | TracingToolResult;\n let toolErr: unknown;\n let hasError = false;\n try {\n rawResult = await config.executeTool(toolCall.name, toolCall.arguments);\n // TracingToolResult:提取 result 字段作为 tool 消息内容\n if (isTracingToolResult(rawResult)) {\n resultStr = stringifyResult(rawResult.result);\n } else {\n resultStr = stringifyResult(rawResult);\n }\n } catch (err) {\n hasError = true;\n toolErr = err;\n resultStr = stringifyError(err);\n rawResult = undefined;\n }\n\n if (enableTracing) {\n const toolEndedAt = nowMs();\n if (isTracingToolResult(rawResult)) {\n // sub-agent 调用:嵌入 sub-trace\n traceEvents!.push({\n type: 'subagent_call',\n turn: turns,\n startedAt: toolStartedAt,\n durationMs: toolEndedAt - toolStartedAt,\n toolCallId: toolCall.id,\n agentName: extractSubAgentName(toolCall.name),\n input: JSON.stringify(toolCall.arguments),\n trace: rawResult.trace,\n result: resultStr,\n });\n } else {\n traceEvents!.push({\n type: 'tool_call',\n turn: turns,\n startedAt: toolStartedAt,\n durationMs: toolEndedAt - toolStartedAt,\n toolCallId: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n result: resultStr,\n error: hasError ? stringifyError(toolErr) : undefined,\n });\n }\n }\n\n messages.push({\n role: 'tool',\n content: resultStr,\n toolCallId: toolCall.id,\n });\n }\n }\n\n throw new ReactLoopError(\n `Max turns (${maxTurns}) exceeded — agent did not converge to a final answer`,\n maxTurns,\n );\n}\n\n// ─── reactLoopStream(流式)──────────────────────────\n\n/**\n * 执行 ReAct 循环(流式)\n *\n * 使用 `provider.stream()` 异步迭代 chunks,yield `deltaContent` + `toolCall` + `toolResult` + `done`。\n *\n * @param input 用户输入\n * @param config 循环配置\n * @yields {ReactLoopStreamChunk} 流式 chunk\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.stream 抛错时立即传播\n */\nexport async function* reactLoopStream(\n input: string,\n config: ReactLoopConfig,\n): AsyncIterable<ReactLoopStreamChunk> {\n const enableTracing = config.enableTracing ?? true;\n const messages = buildInitialMessages(input, config.systemPrompt);\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n const extras = buildRequestExtras(config);\n let totalUsage: LLMUsage | undefined;\n let turns = 0;\n\n while (turns < maxTurns) {\n turns++;\n\n const llmStartedAt = enableTracing ? nowMs() : 0;\n // 浅拷贝快照:该轮发给 LLM 的输入消息(数组新对象,消息对象引用共享)\n const inputSnapshot = enableTracing ? [...messages] : undefined;\n let turnContent = '';\n let toolCalls: LLMToolCall[] | undefined;\n let finishReason: LLMStopReason | undefined;\n let turnUsage: LLMUsage | undefined;\n\n for await (const chunk of config.provider.stream({\n messages: [...messages],\n ...extras,\n })) {\n // 增量内容\n if (typeof chunk.deltaContent === 'string' && chunk.deltaContent.length > 0) {\n turnContent += chunk.deltaContent;\n yield { deltaContent: chunk.deltaContent };\n }\n\n // tool_calls(在最终 chunk 出现,含 id/name/arguments)\n if (chunk.toolCalls && chunk.toolCalls.length > 0) {\n toolCalls = chunk.toolCalls;\n }\n\n // finishReason + usage(在最终 chunk 出现)\n if (chunk.finishReason) {\n finishReason = chunk.finishReason;\n }\n if (chunk.usage) {\n totalUsage = accumulateUsage(totalUsage, chunk.usage);\n turnUsage = chunk.usage;\n }\n }\n\n // 把 assistant 消息加入历史(含 toolCalls,供下一轮 LLM 上下文)\n const assistantMessage: LLMMessage = {\n role: 'assistant',\n content: turnContent,\n };\n if (toolCalls) {\n assistantMessage.toolCalls = toolCalls;\n }\n messages.push(assistantMessage);\n\n if (enableTracing) {\n const llmEndedAt = nowMs();\n yield {\n traceEvent: {\n type: 'llm_call',\n turn: turns,\n startedAt: llmStartedAt,\n durationMs: llmEndedAt - llmStartedAt,\n model: config.model ?? '',\n inputMessages: inputSnapshot!,\n response: assistantMessage,\n stopReason: finishReason ?? 'other',\n usage: turnUsage,\n },\n };\n }\n\n // 非 tool_calls → 循环结束\n if (finishReason !== 'tool_calls' || !toolCalls) {\n yield {\n done: {\n content: turnContent,\n turns,\n stopReason: finishReason ?? 'other',\n usage: totalUsage,\n },\n };\n return;\n }\n\n // 执行每个 tool call\n for (const toolCall of toolCalls) {\n yield { toolCall: { name: toolCall.name, arguments: toolCall.arguments } };\n\n const toolStartedAt = enableTracing ? nowMs() : 0;\n let resultStr: string;\n let rawResult: unknown | TracingToolResult;\n let toolErr: unknown;\n let hasError = false;\n try {\n rawResult = await config.executeTool(toolCall.name, toolCall.arguments);\n if (isTracingToolResult(rawResult)) {\n resultStr = stringifyResult(rawResult.result);\n } else {\n resultStr = stringifyResult(rawResult);\n }\n } catch (err) {\n hasError = true;\n toolErr = err;\n resultStr = stringifyError(err);\n rawResult = undefined;\n }\n\n yield { toolResult: { name: toolCall.name, result: resultStr } };\n\n if (enableTracing) {\n const toolEndedAt = nowMs();\n if (isTracingToolResult(rawResult)) {\n yield {\n traceEvent: {\n type: 'subagent_call',\n turn: turns,\n startedAt: toolStartedAt,\n durationMs: toolEndedAt - toolStartedAt,\n toolCallId: toolCall.id,\n agentName: extractSubAgentName(toolCall.name),\n input: JSON.stringify(toolCall.arguments),\n trace: rawResult.trace,\n result: resultStr,\n },\n };\n } else {\n yield {\n traceEvent: {\n type: 'tool_call',\n turn: turns,\n startedAt: toolStartedAt,\n durationMs: toolEndedAt - toolStartedAt,\n toolCallId: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n result: resultStr,\n error: hasError ? stringifyError(toolErr) : undefined,\n },\n };\n }\n }\n\n messages.push({\n role: 'tool',\n content: resultStr,\n toolCallId: toolCall.id,\n });\n }\n }\n\n throw new ReactLoopError(\n `Max turns (${maxTurns}) exceeded — agent did not converge to a final answer`,\n maxTurns,\n );\n}\n","import type {\n AgentCore,\n AgentMetadata,\n AgentModule,\n AgentToolDescriptor,\n LlmConfig,\n ToolMetadata,\n ToolModule,\n} from '@faapi/faapi';\nimport type { AgentRunOptions } from './agentHandle';\nimport type { LLMProvider, LLMToolDefinition } from './provider';\nimport {\n reactLoop,\n reactLoopStream,\n type ReactLoopConfig,\n type ReactLoopResult,\n type ReactLoopStreamChunk,\n} from './reactLoop';\nimport type { TracingToolResult } from './trace';\n\n/**\n * Agent 类——按 `agent.name` 查找元数据、组装 tool 列表、提供 `run` / `stream` / `asTool`\n *\n * 把 [reactLoop](./reactLoop.md) 与 faapi 核心的 agent/tool 注册表粘合起来:\n * - **组装 tool 列表**——合并 `resolveAgentTools`(agent 显式声明的 `tools`)+ sub-agent\n * - **执行 tool**——`reactLoop` 调 `executeTool(name, args)` 时,Agent 路由:\n * - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用\n * - `agent.` 前缀 → 递归构造 sub-agent 调用(含 `maxAgentDepth` 防护)\n * - **递归防护**——`maxAgentDepth` 限制 agent 调用 agent 的深度\n * - **自定义 run**——agent handler 导出 `run` 函数时,sub-agent 走自定义逻辑\n *\n * 详见 [agent.md](./agent.md)。\n */\n\n/** 默认最大 agent 递归深度(根 agent depth=1,sub-agent 递增) */\nconst DEFAULT_MAX_AGENT_DEPTH = 3;\n\n/**\n * 全局 agent 配置覆盖\n *\n * 来自 faapi.config.ts 的 `agent` 块,提供全局默认值。\n * agent 自身 `config.maxTurns` / `config.model` 优先于全局配置。\n */\nexport interface AgentRuntimeConfig {\n /** 默认最大对话轮数(agent 自身 maxTurns 优先) */\n maxTurns?: number;\n /** agent 调用 agent 的最大递归深度(默认 3) */\n maxAgentDepth?: number;\n /**\n * 启用 tracing 的全局默认值(默认 true)。\n *\n * 开启时 `ReactLoopResult.trace` / `ReactLoopStreamChunk.traceEvent` 填充\n * 结构化调用明细,详见 [trace.md](./trace.md)。\n *\n * 单次调用可通过 `AgentRunOptions.enableTracing` 覆盖。\n * 业务方在生产主路径显式设 `false` 关闭以零开销运行。\n */\n enableTracing?: boolean;\n}\n\n/**\n * tool schema 解析结果\n *\n * 由 Phase 3.5 的注入器实现,提供 JSON Schema(给 LLM)和校验函数(给执行前校验)。\n * - `jsonSchema` —— 发给 LLM 作为 tool 参数描述\n * - `validate` —— 执行前校验 LLM 返回的参数,失败时返回 `{ error }` 回传 LLM 重试\n */\nexport interface ToolSchemaResolution {\n /** tool 参数的 JSON Schema(发给 LLM) */\n jsonSchema: Record<string, unknown>;\n /** 执行前校验函数(成功返回 coerce 后的 value,失败返回 error) */\n validate: (\n input: Record<string, unknown>,\n ) => { ok: true; value: Record<string, unknown> } | { ok: false; error: string };\n}\n\n/**\n * Agent 运行时依赖(依赖注入)\n *\n * Agent 类**不直接 import** faapi 核心的注册表/加载器,而是通过此接口接收访问器函数。\n * 原因:\n * - **可测试**——测试传 mock 访问器,无需启动真实注册表\n * - **解耦**——Agent 类不依赖核心运行时模块\n * - **phase 边界**——Phase 3.4 实现 Agent 类逻辑,Phase 3.5 注入真实访问器\n *\n * 访问器签名与 faapi 核心对称(见 [agent.md](./agent.md) 依赖注入章节)。\n */\nexport interface AgentDeps {\n /** LLM provider 实例映射(key 是 provider 名,来自 config.agent.llms) */\n providers: Map<string, LLMProvider>;\n /** 默认 provider 实例(config.agent.defaultLlm 对应,或 llms 第一个 key) */\n defaultProvider: LLMProvider;\n /** LLM provider 配置映射(含 models,用于 options.model key 解析) */\n llms: Record<string, LlmConfig>;\n /** 默认 provider key(config.agent.defaultLlm,或 llms 第一个 key) */\n defaultLlm: string;\n /** 当前 agent 名 */\n agentName: string;\n /** 项目根目录(Phase 3.5 接线时用于加载器) */\n rootDir: string;\n /** 全局 agent 配置覆盖 */\n config?: AgentRuntimeConfig;\n /** 查 agent LLM 可见元数据(对应 agentRegistry.getAgent,返回 AgentCore) */\n getAgent: (name: string) => AgentCore | undefined;\n /** 查 agent 完整元数据(对应 agentRegistry.getAgentEntry,返回 AgentMetadata 含 filePath/hasRun) */\n getAgentEntry: (name: string) => AgentMetadata | undefined;\n /** 查 tool 元数据(对应 toolRegistry.getTool) */\n getTool: (name: string) => ToolMetadata | undefined;\n /** 解析 agent 可用常规 tool(对应 agentRegistry.resolveAgentTools) */\n resolveAgentTools: (name: string) => ToolMetadata[];\n /** 解析 agent 可调用 sub-agent 列表(对应 agentRegistry.resolveSubAgents,返回 AgentCore[]) */\n resolveSubAgents: (name: string) => AgentCore[];\n /** 动态 import tool handler(对应 loadToolModule) */\n loadToolModule: (filePath: string, functionName: string) => Promise<ToolModule>;\n /** 动态 import agent handler(对应 loadAgentModule,仅 hasRun 参数,无 hasConfig) */\n loadAgentModule: (filePath: string, hasRun: boolean) => Promise<AgentModule>;\n /** tool input 的 schema 解析(Phase 3.5 实现,可选) */\n resolveToolSchema?: (tool: ToolMetadata) => Promise<ToolSchemaResolution | undefined>;\n}\n\n/**\n * Agent 系统级错误\n *\n * agent 未注册等不可恢复错误时抛出(调用方负责捕获)。\n * sub-agent 递归超限用 {@link AgentRecursionError}。\n */\nexport class AgentError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AgentError';\n }\n}\n\n/**\n * sub-agent 递归超 `maxAgentDepth` 时抛出\n *\n * 被 [reactLoop](./reactLoop.md) catch 后错误消息回传 LLM,LLM 可据此调整策略。\n */\nexport class AgentRecursionError extends AgentError {\n /** 配置的 maxAgentDepth 值 */\n readonly maxDepth: number;\n /** 当前递归深度(超出 maxDepth) */\n readonly currentDepth: number;\n\n constructor(maxDepth: number, currentDepth: number) {\n super(\n `Agent recursion depth exceeded: current depth ${currentDepth} > maxAgentDepth ${maxDepth}`,\n );\n this.name = 'AgentRecursionError';\n this.maxDepth = maxDepth;\n this.currentDepth = currentDepth;\n }\n}\n\n/**\n * faapi Agent\n *\n * 组装 [reactLoop](./reactLoop.md) 配置、执行 tool、递归 sub-agent 的运行时入口。\n */\nexport class Agent {\n private readonly deps: AgentDeps;\n /** 当前递归深度(根 agent 为 1,sub-agent 递增) */\n private readonly depth: number;\n /**\n * tool schema 解析缓存(按 tool.name 缓存,含 undefined 结果)\n *\n * `buildToolDefinitions` 组装 LLM tool 列表时解析一次 schema(取 jsonSchema),\n * `executeTool` 执行前校验时复用同一份 schema(取 validate)——\n * 避免每次 tool 执行都重新 `loadToolSchema` + `z.toJSONSchema`。\n *\n * 实例级缓存:sub-agent 各有独立 cache(tool 集合可能不同)。\n */\n private readonly schemaCache = new Map<string, ToolSchemaResolution | undefined>();\n\n /**\n * @param deps 运行时依赖(访问器 + providers Map + defaultProvider + llms + config)\n * @param depth 递归深度(默认 1 = 根 agent;sub-agent 递归时传入 depth+1)\n */\n constructor(deps: AgentDeps, depth: number = 1) {\n this.deps = deps;\n this.depth = depth;\n }\n\n /**\n * 非流式执行——组装 config 调 [reactLoop](./reactLoop.md)\n *\n * reactLoop 不知 agent 名(只关心循环逻辑),返回的 `result.trace.agentName` 为空字符串。\n * 本方法在 reactLoop 返回后填充 `this.deps.agentName`,让顶层 trace 标识\"是哪个 agent 跑的\"。\n *\n * @param input 用户输入\n * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens / enableTracing\n * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))\n * @returns 最终结果(content + messages + turns + stopReason + usage + trace?)\n * @throws {AgentError} agent 未注册\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.complete 抛错时立即传播\n */\n async run(input: string, options?: AgentRunOptions): Promise<ReactLoopResult> {\n const config = await this.buildLoopConfig(options);\n const result = await reactLoop(input, config);\n // reactLoop 不知 agent 名,在此填充顶层 trace.agentName(sub-agent 调本方法时也走此路径)\n if (result.trace) {\n result.trace.agentName = this.deps.agentName;\n }\n return result;\n }\n\n /**\n * 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)\n *\n * @param input 用户输入\n * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens\n * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))\n * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)\n * @throws {AgentError} agent 未注册\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.stream 抛错时立即传播\n */\n async *stream(input: string, options?: AgentRunOptions): AsyncIterable<ReactLoopStreamChunk> {\n const config = await this.buildLoopConfig(options);\n yield* reactLoopStream(input, config);\n }\n\n /**\n * 把自身包装为 `AgentToolDescriptor` 供 LLM 当 tool 调用\n *\n * 与 [agentRegistry.asTool](../../faapi/src/injection/agentRegistry.md) 同构——\n * Agent 类自带此方法便于在注入器场景直接调用(不必再过注册表)。\n *\n * @returns `AgentToolDescriptor` 或 `undefined`(agent 未注册)\n */\n asTool(): AgentToolDescriptor | undefined {\n const meta = this.deps.getAgent(this.deps.agentName);\n if (!meta) return undefined;\n return {\n kind: 'agent',\n name: `agent.${meta.name}`,\n agentName: meta.name,\n description: meta.description,\n metadata: meta,\n };\n }\n\n // ─── 内部方法 ────────────────────────────────────────\n\n /**\n * 查询 tool schema(带缓存)\n *\n * `buildToolDefinitions` 与 `executeTool` 共用此方法——\n * 首次调用触发 `deps.resolveToolSchema`(加载 zod.js + 生成 JSON Schema),\n * 后续命中缓存直接返回(含 `undefined` 结果,用 `has` 区分未解析 vs 解析为空)。\n *\n * `deps.resolveToolSchema` 未提供时直接返回 `undefined`,不写缓存。\n */\n private async getToolSchema(tool: ToolMetadata): Promise<ToolSchemaResolution | undefined> {\n if (!this.deps.resolveToolSchema) return undefined;\n if (this.schemaCache.has(tool.name)) {\n return this.schemaCache.get(tool.name);\n }\n const resolved = await this.deps.resolveToolSchema(tool);\n this.schemaCache.set(tool.name, resolved);\n return resolved;\n }\n\n /**\n * 组装 ReactLoopConfig\n *\n * 1. 查 agent 元数据(未注册抛 AgentError)——用 `getAgent` 拿 AgentCore\n * (LLM-facing 字段:systemPrompt / model / maxTurns)\n * 2. buildToolDefinitions 组装 tool 列表\n * 3. config 字段优先级(高 → 低):`options` > agent 元数据 > 全局 AgentRuntimeConfig / deps.defaultProvider\n *\n * `options.model` 是字符串 key,由 {@link resolveModelKey} 解析为 provider + model\n * (支持 llms key 精确匹配 / `provider/model` 一体化 / 纯 model 名模糊匹配)。\n * 不传 `options.model` 时用 `deps.defaultProvider` + agent 元数据 `config.model`。\n * 详见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。\n */\n private async buildLoopConfig(options?: AgentRunOptions): Promise<ReactLoopConfig> {\n const meta = this.deps.getAgent(this.deps.agentName);\n if (!meta) {\n throw new AgentError(`Agent \"${this.deps.agentName}\" is not registered`);\n }\n\n const tools = await this.buildToolDefinitions();\n\n // 解析 options.model 字符串 key → provider + model\n const { provider, model } = this.resolveModelKey(options?.model, meta);\n\n // enableTracing 优先级:options > deps.config > 默认 true\n // 闭包捕获 enableTracing,通过 executeTool 传递给 executeSubAgent,使其能包装 TracingToolResult\n const enableTracing = options?.enableTracing ?? this.deps.config?.enableTracing ?? true;\n\n return {\n provider,\n systemPrompt: meta.systemPrompt,\n model,\n temperature: options?.temperature,\n maxTokens: options?.maxTokens,\n maxTurns: meta.maxTurns ?? this.deps.config?.maxTurns,\n tools,\n enableTracing,\n executeTool: async (name, args) => this.executeTool(name, args, enableTracing),\n };\n }\n\n /**\n * 解析 `options.model` 字符串 key → provider + model\n *\n * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」:\n * 1. `undefined` → `deps.defaultProvider` + `meta.model`\n * 2. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key\n * 3. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model\n * (要求该 model 在 `deps.llms[provider].models` 里)\n * 4. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找\n * - 唯一 → 该 provider + 该 model\n * - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)\n * - 无 → 抛 `AgentError`\n *\n * @throws {AgentError} key 解析失败(provider/model 不存在或歧义)\n */\n private resolveModelKey(\n key: string | undefined,\n meta: AgentCore,\n ): { provider: LLMProvider; model: string | undefined } {\n // 不传 → 默认 provider + agent 元数据 model\n if (key === undefined) {\n return { provider: this.deps.defaultProvider, model: meta.model };\n }\n\n // 规则 1:精确匹配 providers key(如 'openai')\n const byProviderKey = this.deps.providers.get(key);\n if (byProviderKey) {\n const llmConfig = this.deps.llms[key];\n const firstModel = llmConfig ? Object.keys(llmConfig.models)[0] : undefined;\n return { provider: byProviderKey, model: firstModel ?? meta.model };\n }\n\n // 规则 2:含 '/' → provider/model 形式(如 'openai/gpt-4o')\n if (key.includes('/')) {\n const slashIdx = key.indexOf('/');\n const providerName = key.slice(0, slashIdx);\n const modelName = key.slice(slashIdx + 1);\n const provider = this.deps.providers.get(providerName);\n if (!provider) {\n throw new AgentError(`Unknown provider \"${providerName}\" in model key \"${key}\"`);\n }\n const llmConfig = this.deps.llms[providerName];\n if (!llmConfig || !llmConfig.models[modelName]) {\n throw new AgentError(\n `Model \"${modelName}\" not found in provider \"${providerName}\". Declare it in config.agent.llms.${providerName}.models.`,\n );\n }\n return { provider, model: modelName };\n }\n\n // 规则 3:纯 model 名模糊匹配(如 'gpt-4o')\n const matches: { provider: LLMProvider; providerName: string }[] = [];\n for (const [providerName, provider] of this.deps.providers) {\n const llmConfig = this.deps.llms[providerName];\n if (llmConfig && llmConfig.models[key]) {\n matches.push({ provider, providerName });\n }\n }\n if (matches.length === 1) {\n return { provider: matches[0].provider, model: key };\n }\n if (matches.length > 1) {\n throw new AgentError(\n `Model \"${key}\" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(', ')}). Use \"provider/model\" to disambiguate.`,\n );\n }\n throw new AgentError(\n `Model \"${key}\" not found in any provider. Declare it in config.agent.llms.*.models.`,\n );\n }\n\n /**\n * 组装 LLM 可见 tool 列表\n *\n * 合并两个来源(按 `name` 去重,先入者保留):\n * 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用\n * 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`\n *\n * 每个常规 tool 的 `input`:\n * - `resolveToolSchema` 提供 → 用其 `jsonSchema`\n * - 未提供 / tool 无 `inputTypeName` → 自由 schema `{ type: 'object' }`\n *\n * sub-agent 的 `input` 始终为 `{ type: 'object' }`(agent 参数开放)。\n */\n private async buildToolDefinitions(): Promise<LLMToolDefinition[]> {\n const definitions = new Map<string, LLMToolDefinition>();\n\n // 1. resolveAgentTools(agent 显式声明的 tools 引用)\n for (const tool of this.deps.resolveAgentTools(this.deps.agentName)) {\n if (definitions.has(tool.name)) continue;\n const schemaRes = await this.getToolSchema(tool);\n definitions.set(tool.name, {\n name: tool.name,\n description: tool.description,\n input: schemaRes?.jsonSchema ?? { type: 'object' },\n });\n }\n\n // 2. sub-agent(包装为 agent.<name>)\n for (const subAgent of this.deps.resolveSubAgents(this.deps.agentName)) {\n const name = `agent.${subAgent.name}`;\n if (definitions.has(name)) continue;\n definitions.set(name, {\n name,\n description: subAgent.description,\n input: { type: 'object' },\n });\n }\n\n return Array.from(definitions.values());\n }\n\n /**\n * tool 执行路由(由 reactLoop 调用)\n *\n * - `agent.` 前缀 → {@link executeSubAgent} 递归(含 enableTracing + TracingToolResult 包装)\n * - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用\n *\n * `enableTracing` 由 [buildLoopConfig](#buildLoopConfig) 闭包捕获传入,用于 sub-agent\n * 调用时决定是否包装 [TracingToolResult](./trace.md) 携带 sub-trace。\n *\n * **常规 tool 校验失败**:不抛错,返回 `{ error }` 对象——reactLoop stringify 后\n * 作为 tool 结果回传 LLM,LLM 可据此修正参数重试。\n *\n * **tool 未找到 / 加载失败**:抛错,被 reactLoop catch 后同样回传 LLM。\n */\n private async executeTool(\n name: string,\n args: Record<string, unknown>,\n enableTracing: boolean,\n ): Promise<unknown | TracingToolResult> {\n // sub-agent 递归(携带 enableTracing,使其能包装 TracingToolResult)\n if (name.startsWith('agent.')) {\n return this.executeSubAgent(name.slice(6), args, enableTracing);\n }\n\n // 常规 tool\n const tool = this.deps.getTool(name);\n if (!tool) {\n throw new Error(`Tool \"${name}\" not found`);\n }\n\n // 可选 input 校验\n const schemaRes = await this.getToolSchema(tool);\n let callArgs = args;\n if (schemaRes) {\n const result = schemaRes.validate(args);\n if (!result.ok) {\n // 校验失败:返回 { error } 对象,不调用 handler——错误回传 LLM 重试\n return { error: result.error };\n }\n callArgs = result.value ?? args;\n }\n\n const mod = await this.deps.loadToolModule(tool.filePath, tool.functionName);\n return await mod.handler(callArgs);\n }\n\n /**\n * sub-agent 递归执行\n *\n * 1. `maxAgentDepth` 防护——超限抛 {@link AgentRecursionError}\n * 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`(无 trace,与常规 tool 一致)\n * 3. 无 `run` 时调 `subAgent.run(stringify(args), { enableTracing })` 走默认 reactLoop\n *\n * **tracing 路径**:`enableTracing=true` 时,subAgent.run 返回的 `result.trace`(agentName\n * 已被 `Agent.run` 填为 subName)被包装为 [TracingToolResult](./trace.md) 返回给 reactLoop。\n * reactLoop 通过 `isTracingToolResult` 识别后发出 `subagent_call` 事件,嵌入 sub-trace\n * (递归结构,业务方可还原完整调用树)。`enableTracing=false` 时返回 `result.content`\n * (unknown,与常规 tool 一致,零开销)。\n *\n * **自定义 run 无 trace**:业务方导出 `run` 函数时直接返回业务结果,无法采集 sub-agent\n * 内部明细——需 trace 时应让 sub-agent 走默认 reactLoop(不导出 `run`)。\n *\n * 自定义 run 接收原始 args 对象;默认 reactLoop 接收 stringify 后的 args\n * 作为 user 消息(agent-as-tool input 为开放式 JSON)。\n *\n * 加载 handler.js 用 `getAgentEntry`(返回 AgentMetadata,含 filePath/hasRun),\n * 而非 `getAgent`(返回 AgentCore,无代码加载细节)。DB skill 无文件,\n * `getAgentEntry` 返回 `undefined`,走默认 reactLoop。\n */\n private async executeSubAgent(\n subName: string,\n args: Record<string, unknown>,\n enableTracing: boolean,\n ): Promise<unknown | TracingToolResult> {\n const newDepth = this.depth + 1;\n const maxDepth = this.deps.config?.maxAgentDepth ?? DEFAULT_MAX_AGENT_DEPTH;\n if (newDepth > maxDepth) {\n throw new AgentRecursionError(maxDepth, newDepth);\n }\n\n // 构造子 agent(复用父 deps,仅覆盖 agentName)\n const subDeps: AgentDeps = { ...this.deps, agentName: subName };\n const subAgent = new Agent(subDeps, newDepth);\n\n // 自定义 run:sub-agent handler 导出 run 函数时走自定义逻辑(无 trace)\n // 用 getAgentEntry 拿 AgentMetadata(含 filePath/hasRun),DB skill 无文件走默认 reactLoop\n const entry = this.deps.getAgentEntry(subName);\n if (entry?.hasRun) {\n const mod = await this.deps.loadAgentModule(entry.filePath, entry.hasRun);\n if (mod.run) {\n return await mod.run(args);\n }\n }\n\n // 默认 reactLoop:stringify args 作为 user 消息,传递 enableTracing 让 sub-agent 采集 trace\n const result = await subAgent.run(typeof args === 'string' ? args : JSON.stringify(args), {\n enableTracing,\n });\n\n // enableTracing=true:包装 TracingToolResult,reactLoop 据此发出 subagent_call 事件\n // enableTracing=false:直接返回 content(unknown,与常规 tool 一致,零开销)\n if (enableTracing && result.trace) {\n return {\n __trace: true,\n result: result.content,\n trace: result.trace,\n } satisfies TracingToolResult;\n }\n return result.content;\n }\n}\n","/**\n * @faapi/agent faapi 插件——注册 agent handle 工厂,让 handler 的 `agent` 参数注入可用的 Agent 实例\n *\n * 在 faapi.config.ts 中声明:\n * ```ts\n * import type { FaapiConfig } from '@faapi/faapi';\n *\n * export default {\n * agent: {\n * llms: {\n * openai: {\n * provider: 'openai',\n * apiKey: process.env.OPENAI_API_KEY,\n * baseURL: 'https://api.openai.com/v1',\n * models: { 'gpt-4o': {}, 'gpt-4o-mini': { temperature: 0.5 } },\n * },\n * },\n * defaultLlm: 'openai',\n * defaultAgent: 'researcher',\n * maxTurns: 10,\n * },\n * plugins: ['@faapi/agent'],\n * } satisfies FaapiConfig;\n * ```\n *\n * 插件 setup 时:\n * 1. 遍历 `config.agent.llms` → 每项调 `createProvider` → `Map<providerKey, LLMProvider>`\n * 2. 读 `config.agent.defaultLlm` → `defaultProvider`(未设时用 `llms` 第一个 key)\n * 3. 读 `config.agent.defaultAgent` / `maxTurns` / `maxAgentDepth`\n * 4. 从 `@faapi/faapi` import 注册表/加载器访问器(getAgent / getTool / resolveAgentTools /\n * resolveSubAgents / loadAgentModule / loadToolModule)\n * 5. `registerAgentHandleFactory` 注册工厂——每次请求时构造 [Agent](./agent.md) 实例注入到\n * handler 的 `agent` 参数\n *\n * 配置缺失时(`agent.llms` 或 `agent.defaultAgent` 未设置)跳过工厂注册并打印警告,\n * handler 的 `agent` 参数注入 `undefined`。\n *\n * 详见 [plugin.md](./plugin.md)。\n */\n\nimport {\n registerAgentHandleFactory,\n getAgent,\n getAgentEntry,\n getTool,\n resolveAgentTools,\n resolveSubAgents,\n loadAgentModule,\n loadToolModule,\n loadToolSchema,\n type FaapiPlugin,\n type PluginContext,\n type AgentConfig,\n type ToolMetadata,\n} from '@faapi/faapi';\nimport { z } from 'zod';\nimport { Agent, type AgentRuntimeConfig, type ToolSchemaResolution } from './agent';\nimport type { LLMProvider } from './provider';\nimport { createProvider } from './provider';\n\n/**\n * 加载 tool 的 zod.js → 生成 JSON Schema + 校验函数\n *\n * 模块级函数(非 setup 内闭包)——setup 时用 `rootDir` 偏函数绑定一次,\n * 工厂内直接复用,避免每次请求重建闭包。\n *\n * 详见 [plugin.md](./plugin.md) 的 resolveToolSchema 实现。\n *\n * @param tool tool 元数据(含 filePath / inputTypeName)\n * @param rootDir 项目根目录(用于 dev 按需编译模式)\n * @returns `ToolSchemaResolution` 或 `undefined`(zod.js 不存在 / tool 无 inputTypeName)\n */\nasync function resolveToolSchemaImpl(\n tool: ToolMetadata,\n rootDir: string,\n): Promise<ToolSchemaResolution | undefined> {\n const schemaMod = await loadToolSchema(tool, rootDir);\n if (!schemaMod) return undefined;\n const schema = schemaMod.schema as z.ZodType;\n return {\n jsonSchema: z.toJSONSchema(schema),\n validate: (input) => {\n const result = schema.safeParse(input);\n if (result.success) {\n return { ok: true as const, value: result.data as Record<string, unknown> };\n }\n return { ok: false as const, error: result.error.message };\n },\n } satisfies ToolSchemaResolution;\n}\n\n/**\n * 从 PluginContext.config 读取 agent 配置\n *\n * `agent` 不是 FAAPI_CONFIG_KEYS 的成员(它是 agent 子系统配置,非核心配置),\n * 因此会随自定义业务配置一起传到 `ctx.config`。\n */\nfunction readAgentConfig(ctx: PluginContext): AgentConfig | undefined {\n const raw = ctx.config?.agent;\n if (raw === undefined || raw === null) return undefined;\n return raw as AgentConfig;\n}\n\n/**\n * @faapi/agent faapi 插件入口\n *\n * 在 faapi.config.ts 的 `plugins` 字段中声明 `'@faapi/agent'` 即可启用。\n * 插件加载后,handler 的 `agent` 参数可注入可用的 [AgentHandle](./agentHandle.md)。\n */\nconst agentPlugin: FaapiPlugin = {\n name: '@faapi/agent',\n setup(ctx: PluginContext): void {\n const agentConfig = readAgentConfig(ctx);\n\n // 必要配置检查——缺失时跳过工厂注册,agent 参数注入 undefined\n if (!agentConfig?.llms) {\n console.warn(\n '! @faapi/agent: config.agent.llms not configured, agent parameter injection disabled',\n );\n return;\n }\n if (!agentConfig.defaultAgent) {\n console.warn(\n '! @faapi/agent: config.agent.defaultAgent not configured, agent parameter injection disabled',\n );\n return;\n }\n\n // 创建 LLM provider 实例 Map(key 是 provider 名,来自 config.agent.llms)\n // 单例,所有请求共享;每个 provider 实例对应一个 LlmConfig\n const llms = agentConfig.llms;\n const providers = new Map<string, LLMProvider>();\n for (const [name, llmConfig] of Object.entries(llms)) {\n providers.set(name, createProvider(llmConfig));\n }\n\n // 默认 provider:config.agent.defaultLlm 优先,否则取 llms 第一个 key\n const defaultLlm = agentConfig.defaultLlm ?? Object.keys(llms)[0];\n const defaultProvider = providers.get(defaultLlm);\n if (!defaultProvider) {\n console.warn(\n `! @faapi/agent: config.agent.defaultLlm \"${defaultLlm}\" not found in llms, agent parameter injection disabled`,\n );\n return;\n }\n\n // 全局 agent 运行时配置覆盖\n const runtimeConfig: AgentRuntimeConfig = {\n maxTurns: agentConfig.maxTurns,\n maxAgentDepth: agentConfig.maxAgentDepth,\n };\n\n const rootDir = ctx.rootDir;\n const defaultAgent = agentConfig.defaultAgent;\n // resolveToolSchema 偏函数(setup 内创建一次,工厂内复用,避免每次请求重建闭包)\n const resolveToolSchema = (tool: ToolMetadata) => resolveToolSchemaImpl(tool, rootDir);\n\n // 注册 agent handle 工厂——每次请求时构造 Agent 实例\n // Agent 构造轻量(仅存 deps),实际 LLM 调用在 run/stream 时才发生\n registerAgentHandleFactory(() => {\n return new Agent({\n providers,\n defaultProvider,\n llms,\n defaultLlm,\n agentName: defaultAgent,\n rootDir,\n config: runtimeConfig,\n // 注册表/加载器访问器——从 @faapi/faapi import 的单例模块\n // createAppBase 启动时已水合 agentRegistry / toolRegistry\n // getAgent 返回 AgentCore(LLM-facing);getAgentEntry 返回 AgentMetadata(含 filePath/hasRun,供加载 handler.js)\n getAgent,\n getAgentEntry,\n getTool,\n resolveAgentTools,\n resolveSubAgents,\n // 加载器包装:注入 rootDir 用于 dev 按需编译模式\n loadToolModule: (filePath, functionName) => loadToolModule(filePath, functionName, rootDir),\n loadAgentModule: (filePath, hasRun) => loadAgentModule(filePath, hasRun, rootDir),\n // tool schema 解析(zod.js → JSON Schema + safeParse 校验)\n resolveToolSchema,\n });\n });\n\n console.log(\n `- @faapi/agent: default agent \"${defaultAgent}\" (provider: ${defaultLlm}) available via agent parameter injection`,\n );\n },\n};\n\nexport default agentPlugin;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/providers/openai.ts","../src/provider.ts","../src/trace.ts","../src/reactLoop.ts","../src/agent.ts","../src/plugin.ts"],"sourcesContent":["import type { LlmConfig } from '@faapi/faapi';\nimport { AgentAbortError } from '../provider';\nimport type {\n LLMCompleteRequest,\n LLMMessage,\n LLMProvider,\n LLMResponse,\n LLMStopReason,\n LLMStreamChunk,\n LLMToolCall,\n LLMToolDefinition,\n LLMUsage,\n} from '../provider';\n\n/**\n * OpenAI 兼容 API 的 LLMProvider 实现\n *\n * 通过 `LlmConfig.baseURL` 可指向任意 OpenAI 兼容中转服务\n * (官方 OpenAI / Azure OpenAI / LiteLLM / one-api / 自建网关)。\n *\n * 详见 [openai.md](./openai.md)。\n */\n\n/** OpenAI 默认 API 端点(未设 baseURL 时使用) */\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\n\n/**\n * OpenAI 已知保留字段集合(这些字段在 LlmConfig 中有专门处理,不算\"额外透传\")\n *\n * - `provider` / `apiKey` / `baseURL` —— LlmConfig 结构字段\n * - `model` —— 由 `request.model` 或 `config.models` 第一个 key 决定,不由 config 透传\n * - `models` —— 嵌套级联的 model 配置映射,非 OpenAI API 字段\n */\nconst RESERVED_CONFIG_KEYS = new Set([\n 'provider',\n 'apiKey',\n 'model',\n 'baseURL',\n 'models',\n 'timeoutMs',\n 'maxRetries',\n]);\n\n/**\n * LLM Provider 错误\n *\n * 含 HTTP 状态码(网络错误为 `undefined`)和响应体摘要,\n * 业务方可通过 `instanceof LLMProviderError` 区分 LLM 错误与其他错误。\n */\nexport class LLMProviderError extends Error {\n /** HTTP 状态码(网络错误 / JSON 解析错误为 undefined) */\n readonly status?: number;\n /** 响应体摘要(前 500 字符,便于诊断) */\n readonly body?: string;\n\n constructor(message: string, options?: { status?: number; body?: string; cause?: unknown }) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = 'LLMProviderError';\n this.status = options?.status;\n this.body = options?.body;\n }\n}\n\n/** 内部存储:OpenAI chat completions 请求体(构造阶段) */\ninterface OpenAIRequestBody {\n model?: string;\n messages: unknown[];\n tools?: unknown[];\n stream?: boolean;\n temperature?: number;\n max_tokens?: number;\n [key: string]: unknown;\n}\n\n/** 内部存储:OpenAI tool_call 项(非流式 + 流式统一,index 仅流式有) */\ninterface OpenAIToolCall {\n index?: number;\n id?: string;\n type?: string;\n function?: { name?: string; arguments?: string };\n}\n\n/** 内部存储:OpenAI usage 字段 */\ninterface OpenAIUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}\n\n/** 内部存储:OpenAI chat completions 响应 */\ninterface OpenAIResponseJson {\n choices?: Array<{\n index?: number;\n message?: {\n role?: string;\n content?: string | null;\n tool_calls?: OpenAIToolCall[];\n };\n finish_reason?: string | null;\n }>;\n usage?: OpenAIUsage;\n}\n\n/** 内部存储:OpenAI streaming chunk */\ninterface OpenAIStreamChunkJson {\n choices?: Array<{\n delta?: {\n content?: string | null;\n tool_calls?: OpenAIToolCall[];\n };\n finish_reason?: string | null;\n }>;\n usage?: OpenAIUsage;\n}\n\n/** tool call 累积器(stream 模式按 index 累积 args 字符串) */\ninterface ToolCallAccumulator {\n id?: string;\n name?: string;\n argsString: string;\n}\n\n/**\n * 创建 OpenAI 兼容 LLMProvider\n *\n * @param config LLM 提供方配置(apiKey / baseURL / models + provider 级透传字段,\n * model 级字段在 `models[modelName]` 下覆盖 provider 级同名字段)\n * @returns LLMProvider 实例(含 complete + stream 方法)\n */\nexport function createOpenAIProvider(config: LlmConfig): LLMProvider {\n const baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const apiKey = config.apiKey;\n /** 429/5xx/网络错误的最大重试次数(默认 2,设 0 关闭重试) */\n // 负数归一为 0(关闭重试),避免重试循环不执行导致 throw undefined\n const maxRetries = Math.max(0, typeof config.maxRetries === 'number' ? config.maxRetries : 2);\n\n /**\n * 构造 OpenAI chat completions 请求体\n *\n * 字段优先级(高 → 低):\n * 1. `request.temperature` / `request.maxTokens` —— request 级覆盖\n * 2. `config.models[modelName][key]` —— model 级覆盖(如 model 级 temperature)\n * 3. `config[key]` —— provider 级透传(如 provider 级 temperature)\n *\n * `model` 由 `request.model` 或 `config.models` 第一个 key 决定,\n * 不从 config 透传(避免 config 级 `model` 字段覆盖已解析的 model 名)。\n */\n function buildRequestBody(request: LLMCompleteRequest): OpenAIRequestBody {\n // 确定 model 名:request 优先,否则取 config.models 第一个 key\n const modelName = request.model ?? Object.keys(config.models)[0];\n const modelConfig = modelName ? config.models[modelName] : undefined;\n\n const body: OpenAIRequestBody = {\n model: modelName,\n messages: request.messages.map(toOpenAIMessage),\n };\n\n if (request.tools && request.tools.length > 0) {\n body.tools = request.tools.map(toOpenAITool);\n }\n\n // 合并 config 透传字段:provider 级 + model 级覆盖(model 级优先)\n // 跳过 RESERVED_CONFIG_KEYS(结构字段 + model 名 + models 嵌套配置)\n const mergedConfig: Record<string, unknown> = {};\n for (const key of Object.keys(config)) {\n if (RESERVED_CONFIG_KEYS.has(key)) continue;\n const value = (config as Record<string, unknown>)[key];\n if (value !== undefined) {\n mergedConfig[key] = value;\n }\n }\n if (modelConfig) {\n for (const key of Object.keys(modelConfig)) {\n if (RESERVED_CONFIG_KEYS.has(key)) continue;\n const value = (modelConfig as Record<string, unknown>)[key];\n if (value !== undefined) {\n mergedConfig[key] = value; // 覆盖 provider 级同名字段\n }\n }\n }\n\n // 应用透传字段到 body(跳过已设置的结构字段 model/messages/tools)\n for (const key of Object.keys(mergedConfig)) {\n const value = mergedConfig[key];\n if (value !== undefined && !(key in body)) {\n body[key] = value;\n }\n }\n\n // request 级覆盖(最高优先级)\n if (request.temperature !== undefined) body.temperature = request.temperature;\n if (request.maxTokens !== undefined) body.max_tokens = request.maxTokens;\n\n return body;\n }\n\n /** 构造请求 headers */\n function buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (apiKey) headers.Authorization = `Bearer ${apiKey}`;\n return headers;\n }\n\n /**\n * 调 fetch,统一捕获网络错误\n *\n * abort 错误分类用外部 signal(effectiveSignal 的组合信号 aborted 时无法区分\n * 是外部取消还是超时触发):externalSignal.aborted → AgentAbortError(用户取消);\n * 否则 AbortError → LLMProviderError(timeoutMs 超时)。\n */\n /**\n * abort 类错误分类(authHooks 之外的请求生命周期共用)\n *\n * - 外部 signal 已 aborted → AgentAbortError(用户取消)\n * - TimeoutError(undici 对 AbortSignal.timeout() 的真实拒因)/\n * AbortError(无外部 signal 时部分实现的超时拒因)→ 超时分类\n * - 其余 → Network error\n */\n function classifyAbortError(err: unknown, externalSignal: AbortSignal | undefined): Error {\n if (externalSignal?.aborted) {\n return new AgentAbortError();\n }\n const errName = (err as { name?: string })?.name;\n if (errName === 'AbortError' || errName === 'TimeoutError') {\n return new LLMProviderError(`LLM request timed out after ${config.timeoutMs}ms`);\n }\n const reason = err instanceof Error ? err.message : String(err);\n return new LLMProviderError(`Network error: ${reason}`, { cause: err });\n }\n\n /** 是否为 abort 类拒因(TimeoutError / AbortError)——body 阶段窄分类用 */\n function isAbortLike(err: unknown): boolean {\n const name = (err as { name?: string })?.name;\n return name === 'AbortError' || name === 'TimeoutError';\n }\n\n async function safeFetch(\n url: string,\n init: RequestInit,\n externalSignal: AbortSignal | undefined,\n ): Promise<Response> {\n try {\n return await fetch(url, init);\n } catch (err) {\n throw classifyAbortError(err, externalSignal);\n }\n }\n\n /**\n * 判断 LLMProviderError 是否可重试:429 / 5xx / 网络错误(status undefined)。\n * 4xx 其他(400/401/403/404 等)是确定性错误,重试无意义。\n */\n function isRetryable(err: unknown): boolean {\n if (!(err instanceof LLMProviderError)) return false;\n if (err.status === undefined) return true; // 网络错误\n return err.status === 429 || err.status >= 500;\n }\n\n /** 单次重试等待:优先 Retry-After 头(秒,封顶 30s),否则指数退避 500ms * 2^attempt */\n function retryDelayMs(response: Response | undefined, attempt: number): number {\n const retryAfter = response?.headers.get('retry-after');\n if (retryAfter) {\n const seconds = Number(retryAfter);\n if (!isNaN(seconds) && seconds >= 0) return Math.min(seconds * 1000, 30_000);\n }\n return 500 * 2 ** attempt;\n }\n\n /** 组合外部 signal 与超时信号(LlmConfig.timeoutMs,未设置时不加超时) */\n function effectiveSignal(request: LLMCompleteRequest): AbortSignal | undefined {\n const { signal } = request;\n const timeoutMs = typeof config.timeoutMs === 'number' ? config.timeoutMs : undefined;\n if (!signal && !timeoutMs) return undefined;\n const signals: AbortSignal[] = [];\n if (signal) signals.push(signal);\n if (timeoutMs) signals.push(AbortSignal.timeout(timeoutMs));\n return signals.length === 1 ? signals[0] : AbortSignal.any(signals);\n }\n\n /**\n * fetch + 状态校验 + 自动重试(complete 与 stream 共用)\n *\n * 重试条件:429 / 5xx / 网络错误,退避策略见 retryDelayMs。\n * 每次尝试前预检查外部 signal(已取消则不发请求);init(含超时信号)在\n * 每次尝试时重建——重试获得全新的 timeoutMs 预算(复用已 aborted 的\n * AbortSignal.timeout 会让后续尝试立即/永远失败)。流式响应仅在\n * 「连接建立前」重试——流开始输出后中断不重试(部分内容已消费)。\n */\n async function fetchOkWithRetry(\n url: string,\n baseInit: Omit<RequestInit, 'signal'>,\n request: LLMCompleteRequest,\n maxRetries: number,\n ): Promise<Response> {\n let lastErr: unknown;\n let lastResponse: Response | undefined;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (request.signal?.aborted) {\n throw new AgentAbortError();\n }\n if (attempt > 0) {\n await new Promise((r) => setTimeout(r, retryDelayMs(lastResponse, attempt - 1)));\n }\n const init: RequestInit = { ...baseInit, signal: effectiveSignal(request) };\n let response: Response;\n try {\n response = await safeFetch(url, init, request.signal);\n } catch (err) {\n if (!isRetryable(err)) throw err;\n lastErr = err;\n continue;\n }\n if (response.ok) return response;\n // body 读取失败(如 mock/异常流复用同一 Response)不阻断重试分类\n let excerpt = '<body unreadable>';\n try {\n excerpt = (await response.text()).slice(0, 500);\n } catch {\n // 保持占位符\n }\n const err = new LLMProviderError(`HTTP ${response.status}: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n if (!isRetryable(err)) throw err;\n lastErr = err;\n lastResponse = response;\n }\n throw lastErr;\n }\n\n // ─── complete ──────────────────────────────────────\n\n async function complete(request: LLMCompleteRequest): Promise<LLMResponse> {\n const url = `${baseURL}/chat/completions`;\n const body = buildRequestBody(request);\n const baseInit: Omit<RequestInit, 'signal'> = {\n method: 'POST',\n headers: buildHeaders(),\n body: JSON.stringify(body),\n };\n\n const response = await fetchOkWithRetry(url, baseInit, request, maxRetries);\n\n // body 读取阶段的超时/取消同样归入既有错误分类(而非裸 TimeoutError 冒泡)\n let bodyText: string;\n try {\n bodyText = await response.text();\n } catch (err) {\n if (!isAbortLike(err)) throw err;\n throw classifyAbortError(err, request.signal);\n }\n let json: OpenAIResponseJson;\n try {\n json = JSON.parse(bodyText) as OpenAIResponseJson;\n } catch {\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError(`Invalid JSON response: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n const choice = json.choices?.[0];\n const msg = choice?.message;\n if (!choice || !msg) {\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError('Empty choices in response', {\n status: response.status,\n body: excerpt,\n });\n }\n\n const content = msg.content ?? '';\n const toolCalls = parseToolCalls(msg.tool_calls, bodyText, response.status);\n\n return {\n message: {\n role: 'assistant',\n content,\n toolCalls,\n },\n stopReason: mapStopReason(choice.finish_reason ?? undefined),\n usage: mapUsage(json.usage),\n };\n }\n\n // ─── stream ──────────────────────────────────────\n\n async function* stream(request: LLMCompleteRequest): AsyncIterable<LLMStreamChunk> {\n const url = `${baseURL}/chat/completions`;\n const body = buildRequestBody(request);\n body.stream = true;\n\n const baseInit: Omit<RequestInit, 'signal'> = {\n method: 'POST',\n headers: buildHeaders(),\n body: JSON.stringify(body),\n };\n\n const response = await fetchOkWithRetry(url, baseInit, request, maxRetries);\n\n if (!response.body) {\n throw new LLMProviderError('Response body is null (streaming unsupported)', {\n status: response.status,\n });\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n /** tool call 累积器,按 index 聚合 args 字符串 */\n const accumulators = new Map<number, ToolCallAccumulator>();\n let finishReason: LLMStopReason | undefined;\n let usage: LLMUsage | undefined;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // SSE 事件以两个连续行结束符分隔(规范允许 LF / CRLF / CR)\n while (true) {\n const boundary = findSseEventBoundary(buffer);\n if (!boundary) break;\n const [start, length] = boundary;\n const eventStr = buffer.slice(0, start);\n buffer = buffer.slice(start + length);\n\n const data = extractSSEData(eventStr);\n if (data === null) continue; // 注释行 / 心跳行 / 非 data 行\n\n if (data === '[DONE]') {\n // emit 最终 chunk(含累积的 toolCalls + finishReason + usage)\n yield finalizeStreamChunk(accumulators, finishReason, usage);\n return;\n }\n\n let chunk: OpenAIStreamChunkJson;\n try {\n chunk = JSON.parse(data) as OpenAIStreamChunkJson;\n } catch {\n const excerpt = data.slice(0, 500);\n throw new LLMProviderError(`Invalid SSE chunk: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n const delta = chunk.choices?.[0]?.delta;\n if (delta) {\n // 内容增量\n if (typeof delta.content === 'string' && delta.content.length > 0) {\n yield { deltaContent: delta.content };\n }\n // tool_calls 增量累积\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n accumulateToolCall(accumulators, tc);\n }\n }\n }\n\n const fr = chunk.choices?.[0]?.finish_reason;\n if (fr) finishReason = mapStopReason(fr);\n if (chunk.usage) usage = mapUsage(chunk.usage);\n }\n }\n\n // 流自然结束(无 [DONE]),emit 最终 chunk\n yield finalizeStreamChunk(accumulators, finishReason, usage);\n } catch (err) {\n // body 读取阶段的超时/取消归入既有分类(而非裸 TimeoutError 冒泡);\n // 其余错误(如 chunk JSON 解析的 LLMProviderError)原样冒泡\n if (!isAbortLike(err)) throw err;\n throw classifyAbortError(err, request.signal);\n } finally {\n // 提前 break/异常时主动 cancel 底层流,释放 HTTP 连接(仅 releaseLock 会让\n // undici 连接等到 body 缓冲耗尽或超时才归还)\n try {\n await reader.cancel();\n } catch {\n reader.releaseLock();\n }\n }\n }\n\n return { complete, stream };\n}\n\n// ─── 辅助函数 ──────────────────────────────────────\n\n/** LLMMessage → OpenAI 消息格式 */\nfunction toOpenAIMessage(msg: LLMMessage): unknown {\n const out: Record<string, unknown> = {\n role: msg.role,\n content: msg.content,\n };\n if (msg.toolCallId !== undefined) out.tool_call_id = msg.toolCallId;\n if (msg.toolCalls !== undefined && msg.toolCalls.length > 0) {\n out.tool_calls = msg.toolCalls.map((tc) => ({\n id: tc.id,\n type: 'function',\n function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },\n }));\n }\n return out;\n}\n\n/** LLMToolDefinition → OpenAI tool 定义格式 */\nfunction toOpenAITool(tool: LLMToolDefinition): unknown {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.input,\n },\n };\n}\n\n/** OpenAI tool_calls 数组 → LLMToolCall[],JSON.parse 每个 arguments */\nfunction parseToolCalls(\n toolCalls: OpenAIToolCall[] | undefined,\n bodyText: string,\n status: number,\n): LLMToolCall[] | undefined {\n if (!toolCalls || toolCalls.length === 0) return undefined;\n\n const result: LLMToolCall[] = [];\n for (let i = 0; i < toolCalls.length; i++) {\n const tc = toolCalls[i];\n const argsStr = tc?.function?.arguments ?? '{}';\n let args: Record<string, unknown>;\n try {\n args = JSON.parse(argsStr);\n } catch {\n const excerpt = argsStr.slice(0, 500);\n throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {\n status,\n body: bodyText.slice(0, 500),\n });\n }\n result.push({\n id: tc?.id ?? `call_${i}`,\n name: tc?.function?.name ?? '',\n arguments: args,\n });\n }\n return result;\n}\n\n/** OpenAI finish_reason → LLMStopReason */\nfunction mapStopReason(fr: string | undefined | null): LLMStopReason {\n switch (fr) {\n case 'stop':\n return 'stop';\n case 'tool_calls':\n return 'tool_calls';\n case 'length':\n return 'length';\n case 'content_filter':\n return 'content_filter';\n default:\n return 'other';\n }\n}\n\n/** OpenAI usage → LLMUsage */\nfunction mapUsage(u?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}): LLMUsage | undefined {\n if (!u) return undefined;\n return {\n promptTokens: u.prompt_tokens ?? 0,\n completionTokens: u.completion_tokens ?? 0,\n totalTokens: u.total_tokens ?? 0,\n };\n}\n\n/** 从 SSE 事件块提取 data: 行内容(多行用 \\n 拼接) */\nfunction extractSSEData(event: string): string | null {\n const dataLines: string[] = [];\n // SSE 规范允许 LF / CRLF / CR 三种行结束符\n for (const line of event.split(/\\r\\n|\\n|\\r/)) {\n // 跳过空行 / 注释行 / 心跳行\n if (line === '' || line.startsWith(':')) continue;\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n // 其他字段(event: / id: / retry:)忽略,OpenAI 不用\n }\n if (dataLines.length === 0) return null;\n return dataLines.join('\\n');\n}\n\n/**\n * 查找 buffer 中下一个 SSE 事件边界(两个连续行结束符)\n *\n * SSE 规范(WHATWG Server-Sent Events)的行结束符为 LF、CRLF 或 CR,\n * 事件以两个连续行结束符分隔。部分 OpenAI 兼容网关使用 CRLF 行尾,\n * 仅按 `\\n\\n` 分割会导致事件永不切分、`[DONE]` 比较失败。\n *\n * @returns [边界起始索引, 边界字符长度];buffer 中无完整事件边界时返回 null\n */\nfunction findSseEventBoundary(buffer: string): [number, number] | null {\n let eolCount = 0;\n let boundaryStart = -1;\n for (let i = 0; i < buffer.length; ) {\n const ch = buffer[i];\n let eolLen = 0;\n if (ch === '\\n') {\n eolLen = 1;\n } else if (ch === '\\r') {\n eolLen = buffer[i + 1] === '\\n' ? 2 : 1;\n }\n if (eolLen === 0) {\n // 非行结束符字符:重置计数\n eolCount = 0;\n i++;\n continue;\n }\n eolCount++;\n if (eolCount === 1) {\n boundaryStart = i;\n } else {\n // 第二个连续行结束符结束 → 事件边界\n return [boundaryStart, i + eolLen - boundaryStart];\n }\n i += eolLen;\n }\n return null;\n}\n\n/** 累积 tool call chunk 到按 index 的 Map */\nfunction accumulateToolCall(\n accumulators: Map<number, ToolCallAccumulator>,\n tc: OpenAIToolCall,\n): void {\n const idx = tc.index ?? 0;\n const acc = accumulators.get(idx) ?? { argsString: '' };\n if (tc.id) acc.id = tc.id;\n if (tc.function?.name) acc.name = tc.function.name;\n if (tc.function?.arguments) acc.argsString += tc.function.arguments;\n accumulators.set(idx, acc);\n}\n\n/** 流结束时把累积器转为 LLMToolCall[] + finishReason + usage 一并 emit */\nfunction finalizeStreamChunk(\n accumulators: Map<number, ToolCallAccumulator>,\n finishReason: LLMStopReason | undefined,\n usage: LLMUsage | undefined,\n): LLMStreamChunk {\n const toolCalls: LLMToolCall[] = [];\n if (accumulators.size > 0) {\n const indices = Array.from(accumulators.keys()).sort((a, b) => a - b);\n for (const idx of indices) {\n const acc = accumulators.get(idx)!;\n // 跳过不完整(无 id 或 name)的累积,异常流不应阻止结束\n if (!acc.id || !acc.name) continue;\n let args: Record<string, unknown>;\n try {\n args = acc.argsString ? JSON.parse(acc.argsString) : {};\n } catch {\n const excerpt = acc.argsString.slice(0, 500);\n throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {\n body: excerpt,\n });\n }\n toolCalls.push({\n id: acc.id,\n name: acc.name,\n arguments: args,\n });\n }\n }\n\n const chunk: LLMStreamChunk = {};\n if (toolCalls.length > 0) chunk.toolCalls = toolCalls;\n if (finishReason) chunk.finishReason = finishReason;\n if (usage) chunk.usage = usage;\n return chunk;\n}\n","import type { LlmConfig } from '@faapi/faapi';\nimport { createOpenAIProvider } from './providers/openai';\n\n/**\n * LLM Provider 抽象层\n *\n * 统一 `complete` / `stream` 接口,屏蔽 OpenAI / Anthropic 等 LLM 服务差异。\n * [reactLoop](./reactLoop.md) 与 [Agent 类](./agent.md) 通过此接口调用 LLM,\n * 与具体 provider 解耦。\n *\n * 详见 [provider.md](./provider.md)。\n */\n\n/**\n * 对话消息\n *\n * 四种 role 与 OpenAI chat completions 一致:\n * - `system` —— 系统提示词(agent 的 systemPrompt)\n * - `user` —— 用户输入\n * - `assistant` —— LLM 回复(可能含 toolCalls)\n * - `tool` —— tool 执行结果(需带 toolCallId 标识对应哪个 tool_call)\n */\nexport interface LLMMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n /** 消息内容(assistant 角色 + tool_calls 时可能为空字符串) */\n content: string;\n /** role='tool' 时:对应的 tool_call ID(用于 LLM 关联 tool 结果) */\n toolCallId?: string;\n /** role='assistant' 时:LLM 请求的 tool 调用(reactLoop 据此执行 tool) */\n toolCalls?: LLMToolCall[];\n}\n\n/**\n * LLM 请求的 tool 调用\n *\n * 由 LLM 在 assistant 消息中返回。`arguments` 已 JSON.parse,\n * reactLoop 直接传给 tool 函数。\n */\nexport interface LLMToolCall {\n /** tool call ID(provider 分配,用于匹配 tool 结果) */\n id: string;\n /** tool 名(匹配 LLMToolDefinition.name) */\n name: string;\n /** tool 参数(已 JSON.parse 的对象) */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool 定义\n *\n * 由 [reactLoop](./reactLoop.md) 从 [toolRegistry](../../faapi/src/injection/toolRegistry.md)\n * + [agentRegistry.resolveSubAgents](../../faapi/src/injection/agentRegistry.md) 组装:\n * - 常规 tool:`input` 来自 AST 提取的 zod schema(JSON Schema 形式)\n * - agent-as-tool:`input` 为自由 schema(agent 参数开放)\n */\nexport interface LLMToolDefinition {\n /** tool 名(如 `weather.getWeather` 或 `agent.researcher`) */\n name: string;\n /** tool 描述(对 LLM 可见,引导 LLM 选择调用) */\n description?: string;\n /** JSON Schema 对象(描述 tool 参数结构) */\n input: Record<string, unknown>;\n}\n\n/**\n * complete / stream 的入参\n *\n * `model` / `temperature` / `maxTokens` 优先级高于 [LlmConfig](../../faapi/src/config/configTypes.md)\n * (agent 自身 `config.model` 覆盖全局默认)。\n */\nexport interface LLMCompleteRequest {\n /** 对话消息(含历史 + 当前轮) */\n messages: LLMMessage[];\n /** 可用 tool 列表(未提供时 LLM 不会发起 tool_call) */\n tools?: LLMToolDefinition[];\n /** 覆盖 LlmConfig.model(agent 自身配置优先) */\n model?: string;\n /** 采样温度(0~2) */\n temperature?: number;\n /** 最大生成 token 数 */\n maxTokens?: number;\n /**\n * 取消信号(透传到底层 HTTP 请求)\n *\n * abort 时请求中断并抛 `AgentAbortError`;与 `LlmConfig.timeoutMs` 的\n * 超时信号组合生效(任一触发即中断)。\n */\n signal?: AbortSignal;\n}\n\n/**\n * Agent 执行被取消\n *\n * 外部 `AbortSignal` 触发时抛出(请求前预检查或请求中断)。\n * 业务方通过 `instanceof AgentAbortError` 区分「用户取消」与真实错误——\n * 取消不是故障,不应触发告警/重试逻辑。\n */\nexport class AgentAbortError extends Error {\n constructor(message = 'Agent execution aborted') {\n super(message);\n this.name = 'AgentAbortError';\n }\n}\n\n/**\n * LLM 响应的停止原因\n *\n * - `stop` —— LLM 主动结束(自然结束 / 遇到 stop 序列)\n * - `tool_calls` —— LLM 请求调用 tool(reactLoop 据此进入下一轮)\n * - `length` —— 达到 max_tokens 上限\n * - `content_filter` —— 内容过滤触发\n * - `other` —— 其他原因(未识别的 finish_reason)\n */\nexport type LLMStopReason = 'stop' | 'tool_calls' | 'length' | 'content_filter' | 'other';\n\n/**\n * complete 的返回\n *\n * `message.toolCalls` 不为空时 stopReason 应为 `'tool_calls'`。\n */\nexport interface LLMResponse {\n /** assistant 消息(含 content + 可选 toolCalls) */\n message: LLMMessage;\n /** 停止原因(reactLoop 据此判断是否进入下一轮) */\n stopReason: LLMStopReason;\n /** token 用量(部分 provider 不返回) */\n usage?: LLMUsage;\n}\n\n/**\n * Token 用量\n */\nexport interface LLMUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n}\n\n/**\n * stream 的单个 chunk\n *\n * - 内容流:`deltaContent` 为增量 token\n * - tool 调用:累积完成后在最终 chunk 一并 emit `toolCalls`\n * - 结束:最终 chunk 含 `finishReason` + 可选 `usage`\n */\nexport interface LLMStreamChunk {\n /** 增量内容(streaming token) */\n deltaContent?: string;\n /** 累积完成的 tool 调用(在最终 chunk 出现) */\n toolCalls?: LLMToolCall[];\n /** 结束原因(只在最终 chunk 出现) */\n finishReason?: LLMStopReason;\n /** token 用量(部分 provider 在最终 chunk 提供) */\n usage?: LLMUsage;\n}\n\n/**\n * LLM Provider 抽象接口\n *\n * 实现方:\n * - [createOpenAIProvider](./providers/openai.md) —— OpenAI 兼容 API\n *\n * 消费方:\n * - [reactLoop](./reactLoop.md) Phase 3.3 —— 调 complete / stream 执行 ReAct 循环\n * - [Agent 类](./agent.md) Phase 3.4 —— 构造时持有 provider 实例\n */\nexport interface LLMProvider {\n /** 非流式:阻塞到 LLM 返回完整响应 */\n complete(request: LLMCompleteRequest): Promise<LLMResponse>;\n /** 流式:异步迭代 chunk(增量 token + tool call + 结束) */\n stream(request: LLMCompleteRequest): AsyncIterable<LLMStreamChunk>;\n}\n\n/**\n * LLM Provider 错误\n *\n * 由具体 provider 抛出,包含 HTTP 状态码(网络错误为 `undefined`)和响应体摘要。\n * 业务方可通过 `instanceof LLMProviderError` 区分 LLM 错误与其他错误。\n *\n * 重新从 [./providers/openai](./providers/openai.md) 导出,便于业务方从此模块统一捕获。\n */\nexport { LLMProviderError } from './providers/openai';\n\n/**\n * 按 `config.provider` 路由到对应的 LLM 适配器\n *\n * Phase 3.2 仅支持 `'openai'`,其他值抛错不静默降级(参考 AGENTS.md §6.3)。\n *\n * @param config LLM 提供方配置(来自 faapi.config.ts 的 `agent.llms[key]`)\n * @returns LLMProvider 实例\n * @throws {Error} 当 `config.provider` 不是已支持的值\n */\nexport function createProvider(config: LlmConfig): LLMProvider {\n switch (config.provider) {\n case 'openai':\n return createOpenAIProvider(config);\n default:\n throw new Error(`Unsupported LLM provider: ${String(config.provider)}`);\n }\n}\n","import type { LLMMessage, LLMStopReason, LLMUsage } from './provider';\n\n/**\n * 单次 agent.run() / agent.stream() 的结构化调用明细\n *\n * 默认关闭(`enableTracing` 默认 `false`——opt-in,不开启零开销),\n * 通过 `config.agent.enableTracing: true` 全局开启或单次调用\n * `agent.run(input, { enableTracing: true })` 开启。详见 [trace.md](./trace.md)。\n */\nexport interface AgentTrace {\n /** agent 名 */\n agentName: string;\n /** 开始时间(performance.now() ms,相对进程启动,便于算相对耗时) */\n startedAt: number;\n /** 总耗时 ms(done 时填充,抛错时也填充) */\n durationMs?: number;\n /** 总轮数(与 ReactLoopResult.turns 一致) */\n turns: number;\n /** 累计 token 用量(与 ReactLoopResult.usage 一致) */\n usage?: LLMUsage;\n /** 最终停止原因(与 ReactLoopResult.stopReason 一致) */\n stopReason?: LLMStopReason;\n /** 最终 assistant 内容(与 ReactLoopResult.content 一致) */\n content?: string;\n /** agent 抛错时的错误消息(agent.run 抛错时填充) */\n error?: string;\n /** 按发生顺序的事件列表 */\n events: AgentTraceEvent[];\n}\n\n/**\n * trace 事件(discriminated union,按 `type` 区分)\n *\n * 一次 agent.run() 内的事件序列示例:\n * llm_call(turn=1) → tool_call(turn=1) → llm_call(turn=2) → done\n *\n * sub-agent 调用作为 `subagent_call` 事件,内嵌 sub-trace(递归结构)。\n */\nexport type AgentTraceEvent = LlmCallEvent | ToolCallEvent | SubAgentCallEvent;\n\n/**\n * LLM 调用事件——每轮调 provider.complete() / provider.stream() 触发一次\n */\nexport interface LlmCallEvent {\n type: 'llm_call';\n /** 第几轮(从 1 开始) */\n turn: number;\n startedAt: number;\n durationMs?: number;\n /** 该轮调用的 model 名 */\n model: string;\n /** 该轮发给 LLM 的输入消息快照(浅拷贝数组,消息对象引用共享) */\n inputMessages: LLMMessage[];\n /** 该轮 LLM 返回的 assistant 消息(含 toolCalls 若有) */\n response: LLMMessage;\n /** 该轮的停止原因 */\n stopReason: LLMStopReason;\n /** 该轮的 token 用量(provider 不返回时 undefined) */\n usage?: LLMUsage;\n}\n\n/**\n * 常规 tool 调用事件——executeTool 返回 unknown 时触发\n */\nexport interface ToolCallEvent {\n type: 'tool_call';\n turn: number;\n startedAt: number;\n durationMs?: number;\n /** LLM 分配的 tool_call_id(与 messages 里 tool 消息的 toolCallId 一致) */\n toolCallId: string;\n /** tool 名 */\n name: string;\n /** tool 参数(已 JSON.parse 的对象) */\n arguments: Record<string, unknown>;\n /** tool 执行结果(stringifyResult 后的字符串,与 messages 里 tool 消息一致) */\n result: string;\n /** tool 抛错时填充(result 为 stringifyError 后的字符串) */\n error?: string;\n}\n\n/**\n * sub-agent 调用事件——executeTool 返回 TracingToolResult 时触发\n *\n * sub-agent 的 trace 嵌入 `trace` 字段(递归结构),业务方可还原完整调用树。\n */\nexport interface SubAgentCallEvent {\n type: 'subagent_call';\n turn: number;\n startedAt: number;\n durationMs?: number;\n toolCallId: string;\n /** 被调用的 sub-agent 名 */\n agentName: string;\n /** 喂给 sub-agent 的输入(args JSON.stringify 后) */\n input: string;\n /** sub-agent 自己的 trace(递归) */\n trace: AgentTrace;\n /** sub-agent 返回的最终内容(sub-trace.content 的副本,便于不展开 sub-trace 也能读到结果) */\n result?: string;\n /** sub-agent 抛错时填充 */\n error?: string;\n}\n\n/**\n * sub-agent 调用的特殊返回值——reactLoop 据此识别 sub-agent 调用并发出 subagent_call 事件\n *\n * [Agent.executeSubAgent](./agent.md) 在 `enableTracing=true` 时返回此类型;\n * `enableTracing=false` 时返回 `unknown`(与常规 tool 一致)。\n *\n * `__trace` 是标记字段,避免与普通对象返回值冲突。reactLoop 通过\n * `typeof result === 'object' && result !== null && result.__trace === true`\n * 判断是否为 TracingToolResult。\n */\nexport interface TracingToolResult {\n /** 标记字段(避免与普通对象返回值冲突) */\n __trace: true;\n /** sub-agent 返回的业务结果(stringifyResult 后会作为 tool 消息内容) */\n result: unknown;\n /** sub-agent 自己的 trace */\n trace: AgentTrace;\n}\n\n/**\n * 类型守卫:判断 ToolExecutor 返回值是否为 TracingToolResult\n *\n * reactLoop 用此函数区分 sub-agent 调用(发出 subagent_call 事件)\n * 与常规 tool 调用(发出 tool_call 事件)。\n */\nexport function isTracingToolResult(value: unknown): value is TracingToolResult {\n return (\n typeof value === 'object' && value !== null && (value as { __trace?: unknown }).__trace === true\n );\n}\n","import { AgentAbortError } from './provider';\nimport type {\n LLMMessage,\n LLMProvider,\n LLMStopReason,\n LLMToolCall,\n LLMToolDefinition,\n LLMUsage,\n} from './provider';\nimport {\n isTracingToolResult,\n type AgentTrace,\n type AgentTraceEvent,\n type TracingToolResult,\n} from './trace';\n\n/**\n * ReAct(Reasoning + Acting)循环引擎\n *\n * 反复调 LLM、执行 tool、把结果回传 LLM,直到 LLM 给出最终回答或达到 `maxTurns` 上限。\n *\n * 详见 [reactLoop.md](./reactLoop.md)。\n */\n\n/**\n * Tool 执行函数\n *\n * 由 [Agent 类](./agent.md)提供——reactLoop 不关心 tool 如何被找到和执行。\n * Agent 类的 `executeTool` 实现:\n * - 常规 tool → `loadToolModule` 加载 handler 并调用\n * - agent-as-tool(`agent.` 前缀)→ 递归调子 agent 的 reactLoop(含 `maxAgentDepth` 防护)\n *\n * 返回值可以是任意类型——非 string 会被 JSON.stringify 后回传 LLM。\n *\n * sub-agent 调用时,若 `enableTracing=true`,返回 [TracingToolResult](./trace.md)\n * 携带 sub-agent 的 trace,reactLoop 据此发出 `subagent_call` 事件(嵌套递归 trace)。\n */\nexport type ToolExecutor = (\n name: string,\n args: Record<string, unknown>,\n) => Promise<unknown | TracingToolResult>;\n\n/**\n * reactLoop 配置\n *\n * 由 [Agent 类](./agent.md)组装并传入。\n */\nexport interface ReactLoopConfig {\n /** LLM provider 实例(由 [createProvider](./provider.md) 创建) */\n provider: LLMProvider;\n /** 系统提示词(来自 agent metadata 的 `systemPrompt`) */\n systemPrompt?: string;\n /** 可用 tool 列表(由 `resolveAgentTools` + `resolveSubAgents().map(asTool)` 组装) */\n tools?: LLMToolDefinition[];\n /** tool 执行函数(由 Agent 类提供,路由到常规 tool 或子 agent) */\n executeTool: ToolExecutor;\n /** 最大对话轮数(默认 10,来自 [AgentConfig](../../faapi/src/config/configTypes.md).maxTurns) */\n maxTurns?: number;\n /** 覆盖 LLM 模型名(来自 agent metadata 的 `model`) */\n model?: string;\n /** 采样温度 */\n temperature?: number;\n /** 最大生成 token 数 */\n maxTokens?: number;\n /**\n * 取消信号(透传到每轮 LLM 请求)\n *\n * 循环每轮开始前预检查:已取消时抛 `AgentAbortError`(不再发起 LLM 调用);\n * 执行中取消由 provider 的请求中断传播。tool 执行不被取消(业务自决)。\n */\n signal?: AbortSignal;\n /**\n * 发送给 LLM 的历史 token 预算(近似估算:字符数 / 2;未设置 = 不裁剪,向后兼容)\n *\n * 超预算时从最旧的「轮组」(assistant + 其后全部 tool 结果)开始裁剪,\n * system 与初始 user 永不裁剪,至少保留最近一轮。裁剪只作用于发给 LLM 的\n * 消息副本,本地 `messages` 与 trace 不受影响。详见 reactLoop.md 的历史裁剪章节。\n */\n maxHistoryTokens?: number;\n /**\n * 启用 tracing(默认 true)。开启时填充 `ReactLoopResult.trace` /\n * `ReactLoopStreamChunk.traceEvent`,详见 [trace.md](./trace.md)。\n *\n * 业务方在生产主路径显式传 `false` 关闭以零开销运行。\n */\n enableTracing?: boolean;\n}\n\n/**\n * 非流式循环结果\n */\nexport interface ReactLoopResult {\n /** 最终 assistant 消息内容 */\n content: string;\n /** 完整对话历史(system + user + assistant + tool 消息) */\n messages: LLMMessage[];\n /** 使用的轮数(含最终轮) */\n turns: number;\n /** 最终轮的停止原因 */\n stopReason: LLMStopReason;\n /** 累计 token 用量(多轮累加,provider 不返回时为 `undefined`) */\n usage?: LLMUsage;\n /**\n * 结构化调用明细(`enableTracing=true` 时填充,否则 `undefined` 零开销)。\n * 详见 [trace.md](./trace.md)。\n */\n trace?: AgentTrace;\n}\n\n/**\n * 流式循环的单个 chunk\n *\n * 每个 chunk 至多含一个字段:\n * - `deltaContent` — LLM 增量 token(多次 yield)\n * - `toolCall` — tool 开始执行\n * - `toolResult` — tool 执行完成\n * - `traceEvent` — trace 事件(`enableTracing=true` 时增量推送,与上述字段互斥)\n * - `done` — 循环结束(只 yield 一次)\n */\nexport interface ReactLoopStreamChunk {\n /** LLM 增量 token */\n deltaContent?: string;\n /** tool 开始执行(LLM 请求调用 tool) */\n toolCall?: { name: string; arguments: Record<string, unknown> };\n /** tool 执行完成(含结果) */\n toolResult?: { name: string; result: string };\n /**\n * trace 事件(`enableTracing=true` 时增量推送)。\n * 与 deltaContent / toolCall / toolResult / done 互斥,一个 chunk 至多一个字段。\n */\n traceEvent?: AgentTraceEvent;\n /** 循环结束 */\n done?: {\n content: string;\n turns: number;\n stopReason: LLMStopReason;\n usage?: LLMUsage;\n };\n}\n\n/**\n * reactLoop 系统级错误\n *\n * 目前仅用于 `maxTurns` 超限。tool 执行错误不抛此类型——它们被 catch 后回传 LLM。\n */\nexport class ReactLoopError extends Error {\n /** 配置的 maxTurns 值 */\n readonly maxTurns: number;\n\n constructor(message: string, maxTurns: number) {\n super(message);\n this.name = 'ReactLoopError';\n this.maxTurns = maxTurns;\n }\n}\n\n/** 默认最大轮数 */\nconst DEFAULT_MAX_TURNS = 10;\n\n/** 把 tool 执行结果转为字符串(非 string 自动 JSON.stringify) */\nfunction stringifyResult(result: unknown): string {\n if (typeof result === 'string') return result;\n if (result === undefined) return '';\n return JSON.stringify(result);\n}\n\n/** 把 tool 执行错误转为字符串 */\nfunction stringifyError(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\n/** 累加 usage */\nfunction accumulateUsage(a: LLMUsage | undefined, b: LLMUsage): LLMUsage {\n if (!a) return { ...b };\n return {\n promptTokens: a.promptTokens + b.promptTokens,\n completionTokens: a.completionTokens + b.completionTokens,\n totalTokens: a.totalTokens + b.totalTokens,\n };\n}\n\n/** 构造初始 messages(可选 system + user input) */\nfunction buildInitialMessages(input: string, systemPrompt?: string): LLMMessage[] {\n const messages: LLMMessage[] = [];\n if (systemPrompt) {\n messages.push({ role: 'system', content: systemPrompt });\n }\n messages.push({ role: 'user', content: input });\n return messages;\n}\n\n/** 构造 LLM complete/stream 请求参数(除 messages 外的公共字段) */\n/** 近似 token 估算:字符数 / 2(中英混合保守值,不引入 tokenizer 依赖) */\nfunction estimateTokens(chars: number): number {\n return Math.ceil(chars / 2);\n}\n\nfunction estimateMessageTokens(message: LLMMessage): number {\n let chars = message.content.length;\n if (message.toolCalls) {\n chars += JSON.stringify(message.toolCalls).length;\n }\n if (message.toolCallId) {\n chars += message.toolCallId.length;\n }\n return estimateTokens(chars);\n}\n\n/**\n * 按预算裁剪发给 LLM 的历史(见 reactLoop.md 历史裁剪章节)\n *\n * - 头部保留段(system + 初始 user,直到第一个 assistant)永不裁剪\n * - 轮组(assistant + 其后全部 tool 结果)为原子单位,从最旧开始丢\n * - 至少保留最近一轮(即使其自身超预算,也不发送空历史)\n */\nexport function trimHistory(messages: LLMMessage[], maxTokens: number): LLMMessage[] {\n // 头部:system + 初始 user(第一个 assistant 之前的连续前缀)\n let headEnd = 0;\n while (headEnd < messages.length && messages[headEnd]!.role !== 'assistant') {\n headEnd++;\n }\n const head = messages.slice(0, headEnd);\n\n // 轮组划分:rest 中每个 assistant 开启一个新轮组,其后 tool 消息归属之\n const turns: LLMMessage[][] = [];\n for (const message of messages.slice(headEnd)) {\n if (message.role === 'assistant' || turns.length === 0) {\n turns.push([message]);\n } else {\n turns[turns.length - 1]!.push(message);\n }\n }\n if (turns.length === 0) return messages;\n\n // 从最新往旧收集,预算内尽量多保留;最近一轮无条件保留\n const kept: LLMMessage[][] = [];\n let total = head.reduce((sum, m) => sum + estimateMessageTokens(m), 0);\n for (let i = turns.length - 1; i >= 0; i--) {\n const turnTokens = turns[i]!.reduce((sum, m) => sum + estimateMessageTokens(m), 0);\n if (kept.length > 0 && total + turnTokens > maxTokens) break;\n kept.unshift(turns[i]!);\n total += turnTokens;\n }\n\n return [...head, ...kept.flat()];\n}\n\nfunction buildRequestExtras(config: ReactLoopConfig) {\n return {\n tools: config.tools,\n model: config.model,\n temperature: config.temperature,\n maxTokens: config.maxTokens,\n };\n}\n\n/** 当前时间戳(performance.now() ms,相对进程启动) */\nfunction nowMs(): number {\n return performance.now();\n}\n\n/**\n * 从 sub-agent tool 名提取 agent 名\n *\n * sub-agent tool 命名约定:`agent.<agentName>`(见 [agentRegistry.asTool](../../faapi/src/injection/agentRegistry.md))。\n * 非 `agent.` 前缀的原样返回(用于业务方自定义 sub-agent tool 命名)。\n */\nfunction extractSubAgentName(toolName: string): string {\n const prefix = 'agent.';\n if (toolName.startsWith(prefix)) {\n return toolName.slice(prefix.length);\n }\n return toolName;\n}\n\n// ─── reactLoop(非流式)──────────────────────────────\n\n/**\n * 执行 ReAct 循环(非流式)\n *\n * 反复调 `provider.complete()` → 执行 tool → 回传结果,直到 LLM 返回 `stop`(或其他非 `tool_calls` 原因)或超出 `maxTurns`。\n *\n * @param input 用户输入\n * @param config 循环配置\n * @returns 最终结果(content + messages + turns + stopReason + usage)\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.complete 抛错时立即传播\n */\nexport async function reactLoop(input: string, config: ReactLoopConfig): Promise<ReactLoopResult> {\n const enableTracing = config.enableTracing ?? false;\n const messages = buildInitialMessages(input, config.systemPrompt);\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n const extras = buildRequestExtras(config);\n let totalUsage: LLMUsage | undefined;\n let turns = 0;\n\n // trace 采集容器(enableTracing=false 时不构造,零开销)\n const traceStartedAt = enableTracing ? nowMs() : 0;\n const traceEvents: AgentTraceEvent[] | undefined = enableTracing ? [] : undefined;\n\n while (turns < maxTurns) {\n // 取消预检查:已取消则不再发起本轮 LLM 调用\n if (config.signal?.aborted) {\n throw new AgentAbortError();\n }\n turns++;\n\n const llmStartedAt = enableTracing ? nowMs() : 0;\n // 历史裁剪(maxHistoryTokens):只作用于发给 LLM 的消息副本,本地 messages 不变\n const outgoing = config.maxHistoryTokens\n ? trimHistory(messages, config.maxHistoryTokens)\n : messages;\n // 浅拷贝快照:该轮发给 LLM 的输入消息(数组新对象,消息对象引用共享)\n const inputSnapshot = enableTracing ? [...outgoing] : undefined;\n const response = await config.provider.complete({\n messages: [...outgoing],\n ...extras,\n signal: config.signal,\n });\n\n if (response.usage) {\n totalUsage = accumulateUsage(totalUsage, response.usage);\n }\n\n if (enableTracing) {\n const llmEndedAt = nowMs();\n traceEvents!.push({\n type: 'llm_call',\n turn: turns,\n startedAt: llmStartedAt,\n durationMs: llmEndedAt - llmStartedAt,\n model: config.model ?? '',\n inputMessages: inputSnapshot!,\n response: response.message,\n stopReason: response.stopReason,\n usage: response.usage,\n });\n }\n\n // 把 assistant 消息加入历史\n messages.push(response.message);\n\n // 非 tool_calls → 循环结束\n if (response.stopReason !== 'tool_calls' || !response.message.toolCalls) {\n const traceEndedAt = enableTracing ? nowMs() : 0;\n return {\n content: response.message.content,\n messages,\n turns,\n stopReason: response.stopReason,\n usage: totalUsage,\n trace: enableTracing\n ? {\n agentName: '',\n startedAt: traceStartedAt,\n durationMs: traceEndedAt - traceStartedAt,\n turns,\n usage: totalUsage,\n stopReason: response.stopReason,\n content: response.message.content,\n events: traceEvents!,\n }\n : undefined,\n };\n }\n\n // 并行执行同轮全部 tool_call——多个独立 tool 的总耗时从「各 tool 之和」\n // 降为「最慢一个」。每个 toolCall 独立 try/catch(单个失败不影响其余),\n // 结果按下方的 toolCalls 声明顺序回传(与完成顺序无关,保证 tool 配对语义)。\n // 注:beforeToolCall/afterToolCall 钩子会并发触发,业务方钩子不应依赖调用顺序。\n // 流式路径(reactLoopStream)保持串行——yield 顺序受消费端约束。\n const settled = await Promise.all(\n response.message.toolCalls.map(async (toolCall) => {\n const toolStartedAt = enableTracing ? nowMs() : 0;\n let resultStr: string;\n let rawResult: unknown | TracingToolResult;\n let toolErr: unknown;\n let hasError = false;\n try {\n rawResult = await config.executeTool(toolCall.name, toolCall.arguments);\n // TracingToolResult:提取 result 字段作为 tool 消息内容\n if (isTracingToolResult(rawResult)) {\n resultStr = stringifyResult(rawResult.result);\n } else {\n resultStr = stringifyResult(rawResult);\n }\n } catch (err) {\n hasError = true;\n toolErr = err;\n resultStr = stringifyError(err);\n rawResult = undefined;\n }\n // 结束时间在各自闭包内取——并行执行时若在 Promise.all 之后的串行循环里\n // 统一取,每个 tool 的 durationMs 都会包含等待其他 tool 的时间(全部失真\n // 为「最慢 tool」的耗时)\n const toolEndedAt = enableTracing ? nowMs() : 0;\n return {\n toolCall,\n toolStartedAt,\n toolEndedAt,\n resultStr,\n rawResult,\n toolErr,\n hasError,\n };\n }),\n );\n\n for (const {\n toolCall,\n toolStartedAt,\n toolEndedAt,\n resultStr,\n rawResult,\n toolErr,\n hasError,\n } of settled) {\n if (enableTracing) {\n if (isTracingToolResult(rawResult)) {\n // sub-agent 调用:嵌入 sub-trace\n traceEvents!.push({\n type: 'subagent_call',\n turn: turns,\n startedAt: toolStartedAt,\n durationMs: toolEndedAt - toolStartedAt,\n toolCallId: toolCall.id,\n agentName: extractSubAgentName(toolCall.name),\n input: JSON.stringify(toolCall.arguments),\n trace: rawResult.trace,\n result: resultStr,\n });\n } else {\n traceEvents!.push({\n type: 'tool_call',\n turn: turns,\n startedAt: toolStartedAt,\n durationMs: toolEndedAt - toolStartedAt,\n toolCallId: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n result: resultStr,\n error: hasError ? stringifyError(toolErr) : undefined,\n });\n }\n }\n\n messages.push({\n role: 'tool',\n content: resultStr,\n toolCallId: toolCall.id,\n });\n }\n }\n\n throw new ReactLoopError(\n `Max turns (${maxTurns}) exceeded — agent did not converge to a final answer`,\n maxTurns,\n );\n}\n\n// ─── reactLoopStream(流式)──────────────────────────\n\n/**\n * 执行 ReAct 循环(流式)\n *\n * 使用 `provider.stream()` 异步迭代 chunks,yield `deltaContent` + `toolCall` + `toolResult` + `done`。\n *\n * @param input 用户输入\n * @param config 循环配置\n * @yields {ReactLoopStreamChunk} 流式 chunk\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.stream 抛错时立即传播\n */\nexport async function* reactLoopStream(\n input: string,\n config: ReactLoopConfig,\n): AsyncIterable<ReactLoopStreamChunk> {\n const enableTracing = config.enableTracing ?? false;\n const messages = buildInitialMessages(input, config.systemPrompt);\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n const extras = buildRequestExtras(config);\n let totalUsage: LLMUsage | undefined;\n let turns = 0;\n\n while (turns < maxTurns) {\n // 取消预检查:已取消则不再发起本轮 LLM 调用\n if (config.signal?.aborted) {\n throw new AgentAbortError();\n }\n turns++;\n\n const llmStartedAt = enableTracing ? nowMs() : 0;\n // 历史裁剪(maxHistoryTokens):只作用于发给 LLM 的消息副本,本地 messages 不变\n const outgoing = config.maxHistoryTokens\n ? trimHistory(messages, config.maxHistoryTokens)\n : messages;\n // 浅拷贝快照:该轮发给 LLM 的输入消息(数组新对象,消息对象引用共享)\n const inputSnapshot = enableTracing ? [...outgoing] : undefined;\n let turnContent = '';\n let toolCalls: LLMToolCall[] | undefined;\n let finishReason: LLMStopReason | undefined;\n let turnUsage: LLMUsage | undefined;\n\n for await (const chunk of config.provider.stream({\n messages: [...outgoing],\n ...extras,\n signal: config.signal,\n })) {\n // 增量内容\n if (typeof chunk.deltaContent === 'string' && chunk.deltaContent.length > 0) {\n turnContent += chunk.deltaContent;\n yield { deltaContent: chunk.deltaContent };\n }\n\n // tool_calls(在最终 chunk 出现,含 id/name/arguments)\n if (chunk.toolCalls && chunk.toolCalls.length > 0) {\n toolCalls = chunk.toolCalls;\n }\n\n // finishReason + usage(在最终 chunk 出现)\n if (chunk.finishReason) {\n finishReason = chunk.finishReason;\n }\n if (chunk.usage) {\n totalUsage = accumulateUsage(totalUsage, chunk.usage);\n turnUsage = chunk.usage;\n }\n }\n\n // 把 assistant 消息加入历史(含 toolCalls,供下一轮 LLM 上下文)\n const assistantMessage: LLMMessage = {\n role: 'assistant',\n content: turnContent,\n };\n if (toolCalls) {\n assistantMessage.toolCalls = toolCalls;\n }\n messages.push(assistantMessage);\n\n if (enableTracing) {\n const llmEndedAt = nowMs();\n yield {\n traceEvent: {\n type: 'llm_call',\n turn: turns,\n startedAt: llmStartedAt,\n durationMs: llmEndedAt - llmStartedAt,\n model: config.model ?? '',\n inputMessages: inputSnapshot!,\n response: assistantMessage,\n stopReason: finishReason ?? 'other',\n usage: turnUsage,\n },\n };\n }\n\n // 非 tool_calls → 循环结束\n if (finishReason !== 'tool_calls' || !toolCalls) {\n yield {\n done: {\n content: turnContent,\n turns,\n stopReason: finishReason ?? 'other',\n usage: totalUsage,\n },\n };\n return;\n }\n\n // 执行每个 tool call\n for (const toolCall of toolCalls) {\n yield { toolCall: { name: toolCall.name, arguments: toolCall.arguments } };\n\n const toolStartedAt = enableTracing ? nowMs() : 0;\n let resultStr: string;\n let rawResult: unknown | TracingToolResult;\n let toolErr: unknown;\n let hasError = false;\n try {\n rawResult = await config.executeTool(toolCall.name, toolCall.arguments);\n if (isTracingToolResult(rawResult)) {\n resultStr = stringifyResult(rawResult.result);\n } else {\n resultStr = stringifyResult(rawResult);\n }\n } catch (err) {\n hasError = true;\n toolErr = err;\n resultStr = stringifyError(err);\n rawResult = undefined;\n }\n\n yield { toolResult: { name: toolCall.name, result: resultStr } };\n\n if (enableTracing) {\n const toolEndedAt = nowMs();\n if (isTracingToolResult(rawResult)) {\n yield {\n traceEvent: {\n type: 'subagent_call',\n turn: turns,\n startedAt: toolStartedAt,\n durationMs: toolEndedAt - toolStartedAt,\n toolCallId: toolCall.id,\n agentName: extractSubAgentName(toolCall.name),\n input: JSON.stringify(toolCall.arguments),\n trace: rawResult.trace,\n result: resultStr,\n },\n };\n } else {\n yield {\n traceEvent: {\n type: 'tool_call',\n turn: turns,\n startedAt: toolStartedAt,\n durationMs: toolEndedAt - toolStartedAt,\n toolCallId: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n result: resultStr,\n error: hasError ? stringifyError(toolErr) : undefined,\n },\n };\n }\n }\n\n messages.push({\n role: 'tool',\n content: resultStr,\n toolCallId: toolCall.id,\n });\n }\n }\n\n throw new ReactLoopError(\n `Max turns (${maxTurns}) exceeded — agent did not converge to a final answer`,\n maxTurns,\n );\n}\n","import type {\n AgentCore,\n AgentMetadata,\n AgentModule,\n AgentToolDescriptor,\n FaapiContext,\n LlmConfig,\n ToolMetadata,\n ToolModule,\n} from '@faapi/faapi';\nimport type { AgentRunOptions } from './agentHandle';\nimport type { LLMProvider, LLMToolDefinition } from './provider';\nimport {\n reactLoop,\n reactLoopStream,\n type ReactLoopConfig,\n type ReactLoopResult,\n type ReactLoopStreamChunk,\n} from './reactLoop';\nimport type { TracingToolResult } from './trace';\n\n/**\n * Agent 类——按 `agent.name` 查找元数据、组装 tool 列表、提供 `run` / `stream` / `asTool`\n *\n * 把 [reactLoop](./reactLoop.md) 与 faapi 核心的 agent/tool 注册表粘合起来:\n * - **组装 tool 列表**——合并 `resolveAgentTools`(agent 显式声明的 `tools`)+ sub-agent\n * - **执行 tool**——`reactLoop` 调 `executeTool(name, args)` 时,Agent 路由:\n * - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用\n * - `agent.` 前缀 → 递归构造 sub-agent 调用(含 `maxAgentDepth` 防护)\n * - **递归防护**——`maxAgentDepth` 限制 agent 调用 agent 的深度\n * - **自定义 run**——agent handler 导出 `run` 函数时,sub-agent 走自定义逻辑\n *\n * 详见 [agent.md](./agent.md)。\n */\n\n/** 默认最大 agent 递归深度(根 agent depth=1,sub-agent 递增) */\nconst DEFAULT_MAX_AGENT_DEPTH = 3;\n\n/**\n * 全局 agent 配置覆盖\n *\n * 来自 faapi.config.ts 的 `agent` 块,提供全局默认值。\n * agent 自身 `config.maxTurns` / `config.model` 优先于全局配置。\n */\nexport interface AgentRuntimeConfig {\n /** 默认最大对话轮数(agent 自身 maxTurns 优先) */\n maxTurns?: number;\n /** agent 调用 agent 的最大递归深度(默认 3) */\n maxAgentDepth?: number;\n /** 发送给 LLM 的历史 token 预算(近似估算,未设置 = 不裁剪)——透传 reactLoop,见 reactLoop.md 历史裁剪章节 */\n maxHistoryTokens?: number;\n /**\n * 启用 tracing 的全局默认值(默认 false——opt-in,不开启零开销)。\n *\n * 开启时 `ReactLoopResult.trace` / `ReactLoopStreamChunk.traceEvent` 填充\n * 结构化调用明细,详见 [trace.md](./trace.md)。\n *\n * 单次调用可通过 `AgentRunOptions.enableTracing` 覆盖。\n */\n enableTracing?: boolean;\n /**\n * 执行守卫(authHooks,见 [authHooks.md](./authHooks.md))\n *\n * `executeTool` 最开头调用(`agent.` 分流之前)——同时覆盖常规 tool 与\n * sub-agent 递归。三种返回:`void` 放行;`{ error }` 拒绝(不执行 handler,\n * error 回传 LLM);`{ args }` 改写后放行(多租户场景强制注入可信值,\n * 不信 LLM 传入的标识参数)。\n */\n beforeToolCall?: ToolCallGuardHook;\n /**\n * 审计钩子(authHooks):tool / sub-agent 成功返回后调用,返回值忽略。\n * 异常路径不调用。\n */\n afterToolCall?: AfterToolCallHook;\n /**\n * 可见性过滤(authHooks):`buildToolDefinitions` 组装完 LLM 可见 tools 后\n * 调用,返回过滤后的数组。每次 `run` / `stream` 生效,含 agent-as-tool 项。\n */\n filterTools?: FilterToolsHook;\n}\n\n/**\n * beforeToolCall 的返回守卫\n *\n * - `{ error }`:拒绝执行,error 字符串回传 LLM\n * - `{ args }`:以改写后的参数继续执行\n */\nexport type ToolCallGuard = { error: string } | { args: Record<string, unknown> };\n\n/** 执行守卫钩子签名(ctx 为请求上下文,编程式直调可能为 undefined) */\nexport type ToolCallGuardHook = (\n name: string,\n args: Record<string, unknown>,\n ctx: FaapiContext | undefined,\n) => void | ToolCallGuard;\n\n/** 审计钩子签名(仅成功路径调用) */\nexport type AfterToolCallHook = (\n name: string,\n args: Record<string, unknown>,\n result: unknown,\n ctx: FaapiContext | undefined,\n) => void;\n\n/** 可见性过滤钩子签名 */\nexport type FilterToolsHook = (\n tools: LLMToolDefinition[],\n ctx: FaapiContext | undefined,\n) => LLMToolDefinition[];\n\n/**\n * tool schema 解析结果\n *\n * 由 Phase 3.5 的注入器实现,提供 JSON Schema(给 LLM)和校验函数(给执行前校验)。\n * - `jsonSchema` —— 发给 LLM 作为 tool 参数描述\n * - `validate` —— 执行前校验 LLM 返回的参数,失败时返回 `{ error }` 回传 LLM 重试\n */\nexport interface ToolSchemaResolution {\n /** tool 参数的 JSON Schema(发给 LLM) */\n jsonSchema: Record<string, unknown>;\n /** 执行前校验函数(成功返回 coerce 后的 value,失败返回 error) */\n validate: (\n input: Record<string, unknown>,\n ) => { ok: true; value: Record<string, unknown> } | { ok: false; error: string };\n}\n\n/**\n * Agent 运行时依赖(依赖注入)\n *\n * Agent 类**不直接 import** faapi 核心的注册表/加载器,而是通过此接口接收访问器函数。\n * 原因:\n * - **可测试**——测试传 mock 访问器,无需启动真实注册表\n * - **解耦**——Agent 类不依赖核心运行时模块\n * - **phase 边界**——Phase 3.4 实现 Agent 类逻辑,Phase 3.5 注入真实访问器\n *\n * 访问器签名与 faapi 核心对称(见 [agent.md](./agent.md) 依赖注入章节)。\n */\nexport interface AgentDeps {\n /** LLM provider 实例映射(key 是 provider 名,来自 config.agent.llms) */\n providers: Map<string, LLMProvider>;\n /** 默认 provider 实例(config.agent.defaultLlm 对应,或 llms 第一个 key) */\n defaultProvider: LLMProvider;\n /** LLM provider 配置映射(含 models,用于 options.model key 解析) */\n llms: Record<string, LlmConfig>;\n /** 默认 provider key(config.agent.defaultLlm,或 llms 第一个 key) */\n defaultLlm: string;\n /** 当前 agent 名 */\n agentName: string;\n /** 项目根目录(Phase 3.5 接线时用于加载器) */\n rootDir: string;\n /** 全局 agent 配置覆盖 */\n config?: AgentRuntimeConfig;\n /**\n * 请求上下文(authHooks ctx 传递链,见 [authHooks.md](./authHooks.md))\n *\n * 由 @faapi/agent 工厂捕获(AgentHandleFactory 签名本就接收 ctx)。\n * 编程式直调(测试/自定义启动器)不传,钩子收到 undefined。\n * sub-agent 递归经 subDeps 展开自动传导(同一 HTTP 请求内 ctx 不变)。\n */\n ctx?: FaapiContext;\n /** 查 agent LLM 可见元数据(对应 agentRegistry.getAgent,返回 AgentCore) */\n getAgent: (name: string) => AgentCore | undefined;\n /** 查 agent 完整元数据(对应 agentRegistry.getAgentEntry,返回 AgentMetadata 含 filePath/hasRun) */\n getAgentEntry: (name: string) => AgentMetadata | undefined;\n /** 查 tool 元数据(对应 toolRegistry.getTool) */\n getTool: (name: string) => ToolMetadata | undefined;\n /** 解析 agent 可用常规 tool(对应 agentRegistry.resolveAgentTools) */\n resolveAgentTools: (name: string) => ToolMetadata[];\n /** 解析 agent 可调用 sub-agent 列表(对应 agentRegistry.resolveSubAgents,返回 AgentCore[]) */\n resolveSubAgents: (name: string) => AgentCore[];\n /** 动态 import tool handler(对应 loadToolModule) */\n loadToolModule: (filePath: string, functionName: string) => Promise<ToolModule>;\n /** 动态 import agent handler(对应 loadAgentModule,仅 hasRun 参数,无 hasConfig) */\n loadAgentModule: (filePath: string, hasRun: boolean) => Promise<AgentModule>;\n /** tool input 的 schema 解析(Phase 3.5 实现,可选) */\n resolveToolSchema?: (tool: ToolMetadata) => Promise<ToolSchemaResolution | undefined>;\n}\n\n/**\n * Agent 系统级错误\n *\n * agent 未注册等不可恢复错误时抛出(调用方负责捕获)。\n * sub-agent 递归超限用 {@link AgentRecursionError}。\n */\nexport class AgentError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AgentError';\n }\n}\n\n/**\n * sub-agent 递归超 `maxAgentDepth` 时抛出\n *\n * 被 [reactLoop](./reactLoop.md) catch 后错误消息回传 LLM,LLM 可据此调整策略。\n */\nexport class AgentRecursionError extends AgentError {\n /** 配置的 maxAgentDepth 值 */\n readonly maxDepth: number;\n /** 当前递归深度(超出 maxDepth) */\n readonly currentDepth: number;\n\n constructor(maxDepth: number, currentDepth: number) {\n super(\n `Agent recursion depth exceeded: current depth ${currentDepth} > maxAgentDepth ${maxDepth}`,\n );\n this.name = 'AgentRecursionError';\n this.maxDepth = maxDepth;\n this.currentDepth = currentDepth;\n }\n}\n\n/**\n * faapi Agent\n *\n * 组装 [reactLoop](./reactLoop.md) 配置、执行 tool、递归 sub-agent 的运行时入口。\n */\nexport class Agent {\n private readonly deps: AgentDeps;\n /** 当前递归深度(根 agent 为 1,sub-agent 递增) */\n private readonly depth: number;\n /**\n * tool schema 解析缓存(按 tool.name 缓存,含 undefined 结果)\n *\n * `buildToolDefinitions` 组装 LLM tool 列表时解析一次 schema(取 jsonSchema),\n * `executeTool` 执行前校验时复用同一份 schema(取 validate)——\n * 避免每次 tool 执行都重新 `loadToolSchema` + `z.toJSONSchema`。\n *\n * 实例级缓存:sub-agent 各有独立 cache(tool 集合可能不同)。\n */\n private readonly schemaCache = new Map<string, ToolSchemaResolution | undefined>();\n\n /**\n * @param deps 运行时依赖(访问器 + providers Map + defaultProvider + llms + config)\n * @param depth 递归深度(默认 1 = 根 agent;sub-agent 递归时传入 depth+1)\n */\n constructor(deps: AgentDeps, depth: number = 1) {\n this.deps = deps;\n this.depth = depth;\n }\n\n /**\n * 非流式执行——组装 config 调 [reactLoop](./reactLoop.md)\n *\n * reactLoop 不知 agent 名(只关心循环逻辑),返回的 `result.trace.agentName` 为空字符串。\n * 本方法在 reactLoop 返回后填充 `this.deps.agentName`,让顶层 trace 标识\"是哪个 agent 跑的\"。\n *\n * @param input 用户输入\n * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens / enableTracing\n * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))\n * @returns 最终结果(content + messages + turns + stopReason + usage + trace?)\n * @throws {AgentError} agent 未注册\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.complete 抛错时立即传播\n */\n async run(input: string, options?: AgentRunOptions): Promise<ReactLoopResult> {\n const config = await this.buildLoopConfig(options);\n const result = await reactLoop(input, config);\n // reactLoop 不知 agent 名,在此填充顶层 trace.agentName(sub-agent 调本方法时也走此路径)\n if (result.trace) {\n result.trace.agentName = options?.agent ?? this.deps.agentName;\n }\n return result;\n }\n\n /**\n * 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)\n *\n * @param input 用户输入\n * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens\n * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))\n * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)\n * @throws {AgentError} agent 未注册\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.stream 抛错时立即传播\n */\n async *stream(input: string, options?: AgentRunOptions): AsyncIterable<ReactLoopStreamChunk> {\n const config = await this.buildLoopConfig(options);\n yield* reactLoopStream(input, config);\n }\n\n /**\n * 把自身包装为 `AgentToolDescriptor` 供 LLM 当 tool 调用\n *\n * 与 [agentRegistry.asTool](../../faapi/src/injection/agentRegistry.md) 同构——\n * Agent 类自带此方法便于在注入器场景直接调用(不必再过注册表)。\n *\n * @returns `AgentToolDescriptor` 或 `undefined`(agent 未注册)\n */\n asTool(): AgentToolDescriptor | undefined {\n const meta = this.deps.getAgent(this.deps.agentName);\n if (!meta) return undefined;\n return {\n kind: 'agent',\n name: `agent.${meta.name}`,\n agentName: meta.name,\n description: meta.description,\n metadata: meta,\n };\n }\n\n // ─── 内部方法 ────────────────────────────────────────\n\n /**\n * 查询 tool schema(带缓存)\n *\n * `buildToolDefinitions` 与 `executeTool` 共用此方法——\n * 首次调用触发 `deps.resolveToolSchema`(加载 zod.js + 生成 JSON Schema),\n * 后续命中缓存直接返回(含 `undefined` 结果,用 `has` 区分未解析 vs 解析为空)。\n *\n * `deps.resolveToolSchema` 未提供时直接返回 `undefined`,不写缓存。\n */\n private async getToolSchema(tool: ToolMetadata): Promise<ToolSchemaResolution | undefined> {\n if (!this.deps.resolveToolSchema) return undefined;\n if (this.schemaCache.has(tool.name)) {\n return this.schemaCache.get(tool.name);\n }\n const resolved = await this.deps.resolveToolSchema(tool);\n this.schemaCache.set(tool.name, resolved);\n return resolved;\n }\n\n /**\n * 组装 ReactLoopConfig\n *\n * 1. 解析有效 agent 名:`options.agent` > `deps.agentName`(`config.agent.defaultAgent`)\n * 2. 查 agent 元数据(未注册抛 AgentError)——用 `getAgent` 拿 AgentCore\n * (LLM-facing 字段:systemPrompt / model / maxTurns)\n * 3. buildToolDefinitions 组装 tool 列表(用有效 agent 名查 tools / sub-agents)\n * 4. config 字段优先级(高 → 低):`options` > agent 元数据 > 全局 AgentRuntimeConfig / deps.defaultProvider\n *\n * `options.model` 是字符串 key,由 {@link resolveModelKey} 解析为 provider + model\n * (支持 llms key 精确匹配 / `provider/model` 一体化 / 纯 model 名模糊匹配)。\n * 不传 `options.model` 时用 `deps.defaultProvider` + agent 元数据 `config.model`。\n * 详见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。\n *\n * `options.agent` 覆盖本次调用的 agent 名——不传时用 `deps.agentName`(来自\n * `config.agent.defaultAgent`)。`defaultAgent` 未设且 `options.agent` 未传时抛\n * `AgentError`。\n */\n private async buildLoopConfig(options?: AgentRunOptions): Promise<ReactLoopConfig> {\n const agentName = options?.agent ?? this.deps.agentName;\n const meta = this.deps.getAgent(agentName);\n if (!meta) {\n throw new AgentError(`Agent \"${agentName}\" is not registered`);\n }\n\n const tools = await this.buildToolDefinitions(agentName);\n\n // 解析 options.model 字符串 key → provider + model\n const { provider, model } = this.resolveModelKey(options?.model, meta);\n\n // enableTracing 优先级:options > deps.config > 默认 false(opt-in,零开销)\n // 闭包捕获 enableTracing,通过 executeTool 传递给 executeSubAgent,使其能包装 TracingToolResult\n const enableTracing = options?.enableTracing ?? this.deps.config?.enableTracing ?? false;\n\n return {\n provider,\n systemPrompt: meta.systemPrompt,\n model,\n temperature: options?.temperature,\n maxTokens: options?.maxTokens,\n maxTurns: meta.maxTurns ?? this.deps.config?.maxTurns,\n tools,\n signal: options?.signal,\n enableTracing,\n executeTool: async (name, args) => this.executeTool(name, args, enableTracing),\n };\n }\n\n /**\n * 解析 `options.model` 字符串 key → provider + model\n *\n * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」:\n * 1. `undefined` → `deps.defaultProvider` + `meta.model`\n * 2. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key\n * 3. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model\n * (要求该 model 在 `deps.llms[provider].models` 里)\n * 4. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找\n * - 唯一 → 该 provider + 该 model\n * - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)\n * - 无 → 抛 `AgentError`\n *\n * @throws {AgentError} key 解析失败(provider/model 不存在或歧义)\n */\n private resolveModelKey(\n key: string | undefined,\n meta: AgentCore,\n ): { provider: LLMProvider; model: string | undefined } {\n // 不传 → 默认 provider + agent 元数据 model\n if (key === undefined) {\n return { provider: this.deps.defaultProvider, model: meta.model };\n }\n\n // 规则 1:精确匹配 providers key(如 'openai')\n const byProviderKey = this.deps.providers.get(key);\n if (byProviderKey) {\n const llmConfig = this.deps.llms[key];\n const firstModel = llmConfig ? Object.keys(llmConfig.models)[0] : undefined;\n return { provider: byProviderKey, model: firstModel ?? meta.model };\n }\n\n // 规则 2:含 '/' → provider/model 形式(如 'openai/gpt-4o')\n if (key.includes('/')) {\n const slashIdx = key.indexOf('/');\n const providerName = key.slice(0, slashIdx);\n const modelName = key.slice(slashIdx + 1);\n const provider = this.deps.providers.get(providerName);\n if (!provider) {\n throw new AgentError(`Unknown provider \"${providerName}\" in model key \"${key}\"`);\n }\n const llmConfig = this.deps.llms[providerName];\n if (!llmConfig || !llmConfig.models[modelName]) {\n throw new AgentError(\n `Model \"${modelName}\" not found in provider \"${providerName}\". Declare it in config.agent.llms.${providerName}.models.`,\n );\n }\n return { provider, model: modelName };\n }\n\n // 规则 3:纯 model 名模糊匹配(如 'gpt-4o')\n const matches: { provider: LLMProvider; providerName: string }[] = [];\n for (const [providerName, provider] of this.deps.providers) {\n const llmConfig = this.deps.llms[providerName];\n if (llmConfig && llmConfig.models[key]) {\n matches.push({ provider, providerName });\n }\n }\n if (matches.length === 1) {\n return { provider: matches[0]!.provider, model: key };\n }\n if (matches.length > 1) {\n throw new AgentError(\n `Model \"${key}\" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(', ')}). Use \"provider/model\" to disambiguate.`,\n );\n }\n throw new AgentError(\n `Model \"${key}\" not found in any provider. Declare it in config.agent.llms.*.models.`,\n );\n }\n\n /**\n * 组装 LLM 可见 tool 列表\n *\n * 合并两个来源(按 `name` 去重,先入者保留):\n * 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用\n * 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`\n *\n * 每个常规 tool 的 `input`:\n * - `resolveToolSchema` 提供 → 用其 `jsonSchema`\n * - 未提供 / tool 无 `inputTypeName` → 自由 schema `{ type: 'object' }`\n *\n * sub-agent 的 `input` 始终为 `{ type: 'object' }`(agent 参数开放)。\n */\n private async buildToolDefinitions(agentName: string): Promise<LLMToolDefinition[]> {\n const definitions = new Map<string, LLMToolDefinition>();\n\n // 1. resolveAgentTools(agent 显式声明的 tools 引用)\n for (const tool of this.deps.resolveAgentTools(agentName)) {\n if (definitions.has(tool.name)) continue;\n const schemaRes = await this.getToolSchema(tool);\n definitions.set(tool.name, {\n name: tool.name,\n description: tool.description,\n input: schemaRes?.jsonSchema ?? { type: 'object' },\n });\n }\n\n // 2. sub-agent(包装为 agent.<name>)\n for (const subAgent of this.deps.resolveSubAgents(agentName)) {\n const name = `agent.${subAgent.name}`;\n if (definitions.has(name)) continue;\n definitions.set(name, {\n name,\n description: subAgent.description,\n input: { type: 'object' },\n });\n }\n\n // 可见性过滤(authHooks):无权 tool 不进 LLM 的 tools 清单(每次 run/stream 生效)\n const defs = Array.from(definitions.values());\n const filtered = this.deps.config?.filterTools?.(defs, this.deps.ctx);\n return filtered ?? defs;\n }\n\n /**\n * tool 执行路由(由 reactLoop 调用)\n *\n * - `agent.` 前缀 → {@link executeSubAgent} 递归(含 enableTracing + TracingToolResult 包装)\n * - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用\n *\n * `enableTracing` 由 [buildLoopConfig](#buildLoopConfig) 闭包捕获传入,用于 sub-agent\n * 调用时决定是否包装 [TracingToolResult](./trace.md) 携带 sub-trace。\n *\n * **常规 tool 校验失败**:不抛错,返回 `{ error }` 对象——reactLoop stringify 后\n * 作为 tool 结果回传 LLM,LLM 可据此修正参数重试。\n *\n * **tool 未找到 / 加载失败**:抛错,被 reactLoop catch 后同样回传 LLM。\n */\n private async executeTool(\n rawName: string,\n rawArgs: Record<string, unknown>,\n enableTracing: boolean,\n ): Promise<unknown | TracingToolResult> {\n // 执行守卫(authHooks):在 agent. 分流之前——一个钩子同时覆盖常规 tool\n // 与 sub-agent 递归。拒绝时不执行目标,守卫的 error 回传 LLM;\n // 改写时以守卫返回的 args 继续(多租户场景强制注入可信值)\n const name = rawName;\n let args = rawArgs;\n const guard = this.deps.config?.beforeToolCall?.(name, args, this.deps.ctx);\n if (guard) {\n if ('error' in guard) return { error: guard.error };\n if ('args' in guard) args = guard.args;\n }\n\n // 执行白名单:只允许 agent 声明的 tools / sub-agents。`tools`/`agents` 声明不只是\n // LLM 可见性过滤——LLM 幻觉或被提示注入时可能请求未声明的任意已注册 tool\n // (如管理类 tool),执行前按声明集合强制校验,未声明一律拒绝(错误回传 LLM,\n // 与参数校验失败语义一致)。sub-agent 递归时每个 depth 层按自己的声明集合校验。\n const declared = new Set<string>();\n for (const tool of this.deps.resolveAgentTools(this.deps.agentName)) {\n declared.add(tool.name);\n }\n for (const sub of this.deps.resolveSubAgents(this.deps.agentName)) {\n declared.add(`agent.${sub.name}`);\n }\n if (!declared.has(name)) {\n return {\n error: `Tool \"${name}\" is not declared by agent \"${this.deps.agentName}\" (add it to the agent's tools/agents declaration)`,\n };\n }\n\n // sub-agent 递归(携带 enableTracing,使其能包装 TracingToolResult)\n if (name.startsWith('agent.')) {\n return await this.executeSubAgent(name.slice(6), args, enableTracing);\n }\n\n // 常规 tool\n const tool = this.deps.getTool(name);\n if (!tool) {\n throw new Error(`Tool \"${name}\" not found`);\n }\n\n // 可选 input 校验\n const schemaRes = await this.getToolSchema(tool);\n let callArgs = args;\n if (schemaRes) {\n const result = schemaRes.validate(args);\n if (!result.ok) {\n // 校验失败:返回 { error } 对象,不调用 handler——错误回传 LLM 重试\n return { error: result.error };\n }\n callArgs = result.value ?? args;\n }\n\n const mod = await this.deps.loadToolModule(tool.filePath, tool.functionName);\n const result = await mod.handler(callArgs, this.deps.ctx);\n this.deps.config?.afterToolCall?.(name, args, result, this.deps.ctx);\n return result;\n }\n\n /**\n * sub-agent 递归执行\n *\n * 1. `maxAgentDepth` 防护——超限抛 {@link AgentRecursionError}\n * 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`(无 trace,与常规 tool 一致)\n * 3. 无 `run` 时调 `subAgent.run(stringify(args), { enableTracing })` 走默认 reactLoop\n *\n * **tracing 路径**:`enableTracing=true` 时,subAgent.run 返回的 `result.trace`(agentName\n * 已被 `Agent.run` 填为 subName)被包装为 [TracingToolResult](./trace.md) 返回给 reactLoop。\n * reactLoop 通过 `isTracingToolResult` 识别后发出 `subagent_call` 事件,嵌入 sub-trace\n * (递归结构,业务方可还原完整调用树)。`enableTracing=false` 时返回 `result.content`\n * (unknown,与常规 tool 一致,零开销)。\n *\n * **自定义 run 无 trace**:业务方导出 `run` 函数时直接返回业务结果,无法采集 sub-agent\n * 内部明细——需 trace 时应让 sub-agent 走默认 reactLoop(不导出 `run`)。\n *\n * 自定义 run 接收原始 args 对象;默认 reactLoop 接收 stringify 后的 args\n * 作为 user 消息(agent-as-tool input 为开放式 JSON)。\n *\n * 加载 handler.js 用 `getAgentEntry`(返回 AgentMetadata,含 filePath/hasRun),\n * 而非 `getAgent`(返回 AgentCore,无代码加载细节)。DB skill 无文件,\n * `getAgentEntry` 返回 `undefined`,走默认 reactLoop。\n */\n private async executeSubAgent(\n subName: string,\n args: Record<string, unknown>,\n enableTracing: boolean,\n ): Promise<unknown | TracingToolResult> {\n const newDepth = this.depth + 1;\n const maxDepth = this.deps.config?.maxAgentDepth ?? DEFAULT_MAX_AGENT_DEPTH;\n if (newDepth > maxDepth) {\n throw new AgentRecursionError(maxDepth, newDepth);\n }\n\n // 构造子 agent(复用父 deps,仅覆盖 agentName)\n const subDeps: AgentDeps = { ...this.deps, agentName: subName };\n const subAgent = new Agent(subDeps, newDepth);\n\n // 自定义 run:sub-agent handler 导出 run 函数时走自定义逻辑(无 trace)\n // 用 getAgentEntry 拿 AgentMetadata(含 filePath/hasRun),DB skill 无文件走默认 reactLoop\n const entry = this.deps.getAgentEntry(subName);\n if (entry?.hasRun) {\n const mod = await this.deps.loadAgentModule(entry.filePath, entry.hasRun);\n if (mod.run) {\n const result = await mod.run(args, this.deps.ctx);\n this.deps.config?.afterToolCall?.(`agent.${subName}`, args, result, this.deps.ctx);\n return result;\n }\n }\n\n // 默认 reactLoop:stringify args 作为 user 消息,传递 enableTracing 让 sub-agent 采集 trace\n const result = await subAgent.run(typeof args === 'string' ? args : JSON.stringify(args), {\n enableTracing,\n });\n\n // enableTracing=true:包装 TracingToolResult,reactLoop 据此发出 subagent_call 事件\n // enableTracing=false:直接返回 content(unknown,与常规 tool 一致,零开销)\n this.deps.config?.afterToolCall?.(`agent.${subName}`, args, result.content, this.deps.ctx);\n if (enableTracing && result.trace) {\n return {\n __trace: true,\n result: result.content,\n trace: result.trace,\n } satisfies TracingToolResult;\n }\n return result.content;\n }\n}\n","/**\n * @faapi/agent faapi 插件——注册 agent handle 工厂,让 handler 的 `agent` 参数注入可用的 Agent 实例\n *\n * 在 faapi.config.ts 中声明:\n * ```ts\n * import type { FaapiConfig } from '@faapi/faapi';\n *\n * export default {\n * agent: {\n * llms: {\n * openai: {\n * provider: 'openai',\n * apiKey: process.env.OPENAI_API_KEY,\n * baseURL: 'https://api.openai.com/v1',\n * models: { 'gpt-4o': {}, 'gpt-4o-mini': { temperature: 0.5 } },\n * },\n * },\n * defaultLlm: 'openai',\n * defaultAgent: 'researcher',\n * maxTurns: 10,\n * },\n * plugins: ['@faapi/agent'],\n * } satisfies FaapiConfig;\n * ```\n *\n * 插件 setup 时:\n * 1. 遍历 `config.agent.llms` → 每项调 `createProvider` → `Map<providerKey, LLMProvider>`\n * 2. 读 `config.agent.defaultLlm` → `defaultProvider`(未设时用 `llms` 第一个 key)\n * 3. 读 `config.agent.defaultAgent`(可选) / `maxTurns` / `maxAgentDepth`\n * 4. 从 `@faapi/faapi` import 注册表/加载器访问器(getAgent / getTool / resolveAgentTools /\n * resolveSubAgents / loadAgentModule / loadToolModule)\n * 5. `registerAgentHandleFactory` 注册工厂——每次请求时构造 [Agent](./agent.md) 实例注入到\n * handler 的 `agent` 参数\n *\n * 配置缺失时(`agent.llms` 未设置)跳过工厂注册并打印警告,\n * handler 的 `agent` 参数注入 `undefined`。\n *\n * `defaultAgent` 可选——未设时 handler 需通过 `agent.run(input, { agent: 'name' })`\n * 显式指定 agent 名。\n *\n * 详见 [plugin.md](./plugin.md)。\n */\n\nimport {\n loadAgentModule,\n loadToolModule,\n loadToolSchema,\n getToolSchemaPath,\n type FaapiPlugin,\n type PluginContext,\n type AgentConfig,\n type ToolMetadata,\n} from '@faapi/faapi';\nimport { statSync } from 'node:fs';\nimport { z } from 'zod';\nimport { Agent, type AgentRuntimeConfig, type ToolSchemaResolution } from './agent';\nimport type { LLMProvider } from './provider';\nimport { createProvider } from './provider';\n\n/**\n * 加载 tool 的 zod.js → 生成 JSON Schema + 校验函数\n *\n * 模块级函数(非 setup 内闭包)——setup 时用 `rootDir` 偏函数绑定一次,\n * 工厂内直接复用,避免每次请求重建闭包。\n *\n * 详见 [plugin.md](./plugin.md) 的 resolveToolSchema 实现。\n *\n * @param tool tool 元数据(含 filePath / inputTypeName)\n * @param rootDir 项目根目录(用于 dev 按需编译模式)\n * @returns `ToolSchemaResolution` 或 `undefined`(zod.js 不存在 / tool 无 inputTypeName)\n */\nasync function resolveToolSchemaImpl(\n tool: ToolMetadata,\n rootDir: string,\n): Promise<ToolSchemaResolution | undefined> {\n const schemaMod = await loadToolSchema(tool, rootDir);\n if (!schemaMod) return undefined;\n const schema = schemaMod.schema as z.ZodType;\n return {\n jsonSchema: z.toJSONSchema(schema),\n validate: (input) => {\n const result = schema.safeParse(input);\n if (result.success) {\n return { ok: true as const, value: result.data as Record<string, unknown> };\n }\n return { ok: false as const, error: result.error.message };\n },\n } satisfies ToolSchemaResolution;\n}\n\n/**\n * 从 PluginContext.config 读取 agent 配置\n *\n * `agent` 不是 FAAPI_CONFIG_KEYS 的成员(它是 agent 子系统配置,非核心配置),\n * 因此会随自定义业务配置一起传到 `ctx.config`。\n */\nfunction readAgentConfig(ctx: PluginContext): AgentConfig | undefined {\n const raw = ctx.config?.agent;\n if (raw === undefined || raw === null) return undefined;\n return raw as AgentConfig;\n}\n\n/**\n * @faapi/agent faapi 插件入口\n *\n * 在 faapi.config.ts 的 `plugins` 字段中声明 `'@faapi/agent'` 即可启用。\n * 插件加载后,handler 的 `agent` 参数可注入可用的 [AgentHandle](./agentHandle.md)。\n */\nconst agentPlugin: FaapiPlugin = {\n name: '@faapi/agent',\n setup(ctx: PluginContext): void {\n // app 级注册表实例(每个 app 独立,随 app 生命周期)\n const registries = ctx.registries;\n const agentConfig = readAgentConfig(ctx);\n // 必要配置检查——llms 缺失时跳过工厂注册,agent 参数注入 undefined\n if (!agentConfig?.llms) {\n console.warn(\n '! @faapi/agent: config.agent.llms not configured, agent parameter injection disabled',\n );\n return;\n }\n\n // defaultAgent 可选——未设时 handler 需通过 agent.run(input, { agent: 'name' }) 显式指定\n const defaultAgent = agentConfig.defaultAgent ?? '';\n\n // 创建 LLM provider 实例 Map(key 是 provider 名,来自 config.agent.llms)\n // 单例,所有请求共享;每个 provider 实例对应一个 LlmConfig\n const llms = agentConfig.llms;\n const providers = new Map<string, LLMProvider>();\n for (const [name, llmConfig] of Object.entries(llms)) {\n providers.set(name, createProvider(llmConfig));\n }\n\n // 默认 provider:config.agent.defaultLlm 优先,否则取 llms 第一个 key\n // llms 非空已由上方守卫保证(空时提前 return),keys[0] 必然存在\n const defaultLlm = agentConfig.defaultLlm ?? Object.keys(llms)[0]!;\n const defaultProvider = providers.get(defaultLlm);\n if (!defaultProvider) {\n console.warn(\n `! @faapi/agent: config.agent.defaultLlm \"${defaultLlm}\" not found in llms, agent parameter injection disabled`,\n );\n return;\n }\n\n // 全局 agent 运行时配置覆盖\n const runtimeConfig: AgentRuntimeConfig = {\n maxTurns: agentConfig.maxTurns,\n maxAgentDepth: agentConfig.maxAgentDepth,\n maxHistoryTokens: agentConfig.maxHistoryTokens,\n // 鉴权钩子(authHooks,见 ./authHooks.md)——业务方在 config.agent 声明\n beforeToolCall: agentConfig.beforeToolCall,\n afterToolCall: agentConfig.afterToolCall,\n filterTools: agentConfig.filterTools,\n };\n\n const rootDir = ctx.rootDir;\n // 跨请求 schema 缓存(setup 闭包级,工厂每次请求 new Agent 但共享此缓存)\n //\n // Agent 工厂每请求构造新实例,实例级 schemaCache(agent.ts)随实例丢弃——\n // 若无此缓存,每个请求都要重新 loadToolSchema(dynamic import + existsSync)+\n // z.toJSONSchema(CPU 密集)。缓存键为 `zodPath#inputTypeName`,值携带 zod.js 的\n // mtime:每次查找 statSync 一次(与原 loadToolSchema 内部的 existsSync 同级开销,\n // 非新增 IO),mtime 变化即重新解析——dev reloadTools 重生成 zod.js 后自愈,\n // prod 产物固化下永远命中,无需 faapi 核心 reload 链路通知本插件。\n const schemaCache = new Map<\n string,\n { mtimeMs: number; resolution: Promise<ToolSchemaResolution | undefined> }\n >();\n const resolveToolSchema = (tool: ToolMetadata): Promise<ToolSchemaResolution | undefined> => {\n const zodPath = getToolSchemaPath(tool, rootDir);\n const key = `${zodPath}#${tool.inputTypeName ?? ''}`;\n let mtimeMs = -1;\n try {\n mtimeMs = statSync(zodPath).mtimeMs;\n } catch {\n // zod.js 不存在(无 inputTypeName / 尚未生成)→ mtimeMs 保持 -1\n }\n const hit = schemaCache.get(key);\n if (hit && hit.mtimeMs === mtimeMs) {\n return hit.resolution;\n }\n // in-flight Promise 直接缓存:同一 tool 的并发请求共享同一次解析\n const resolution = resolveToolSchemaImpl(tool, rootDir);\n schemaCache.set(key, { mtimeMs, resolution });\n return resolution;\n };\n\n // 注册 agent handle 工厂——每次请求时构造 Agent 实例\n // Agent 构造轻量(仅存 deps),实际 LLM 调用在 run/stream 时才发生\n // 方案 A:注册到 **app 实例**的 agentHandle store(ctx.registries),\n // 多 app 同进程互不覆盖;deps 读同套实例注册表(createAppBase 已水合)\n ctx.registries.agentHandle.register((ctx) => {\n return new Agent({\n providers,\n defaultProvider,\n llms,\n defaultLlm,\n agentName: defaultAgent ?? '',\n rootDir,\n config: runtimeConfig,\n // ctx 传递链(authHooks):捕获请求上下文,tool handler / sub-agent /\n // 鉴权钩子均可读取中间件塞入的身份信息(ctx.user / ctx.workspace 等)\n ctx,\n // 注册表访问器——app 实例(PluginContext.registries),非全局单例\n // getAgent 返回 AgentCore(LLM-facing);getAgentEntry 返回 AgentMetadata(含 filePath/hasRun,供加载 handler.js)\n getAgent: registries.agent.getAgent,\n getAgentEntry: registries.agent.getAgentEntry,\n getTool: registries.tool.get,\n resolveAgentTools: registries.agent.resolveAgentTools,\n resolveSubAgents: registries.agent.resolveSubAgents,\n // 加载器包装:注入 rootDir 用于 dev 按需编译模式\n loadToolModule: (filePath, functionName) => loadToolModule(filePath, functionName, rootDir),\n loadAgentModule: (filePath, hasRun) => loadAgentModule(filePath, hasRun, rootDir),\n // tool schema 解析(zod.js → JSON Schema + safeParse 校验)\n resolveToolSchema,\n });\n });\n\n console.log(\n defaultAgent\n ? `- @faapi/agent: default agent \"${defaultAgent}\" (provider: ${defaultLlm}) available via agent parameter injection`\n : `- @faapi/agent: no defaultAgent set — use agent.run(input, { agent: 'name' }) to specify agent (provider: ${defaultLlm})`,\n );\n },\n};\n\nexport default agentPlugin;\n"],"mappings":"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","names":["maxRetries","err","tool","result","ctx"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faapi/agent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Agent runtime for faapi — LLM-driven ReAct loop over scanned agent / tool registries",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,18 +19,18 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@types/node": "^
|
|
23
|
-
"@vitest/coverage-v8": "
|
|
22
|
+
"@types/node": "^24.3.0",
|
|
23
|
+
"@vitest/coverage-v8": "4",
|
|
24
24
|
"tsup": "^8.4.0",
|
|
25
25
|
"typescript": "^5.7.0",
|
|
26
|
-
"vitest": "^
|
|
26
|
+
"vitest": "^4.1.11",
|
|
27
27
|
"zod": "^4.4.3",
|
|
28
|
-
"@faapi/faapi": "
|
|
28
|
+
"@faapi/faapi": "4.0.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"typescript": ">=5.7.0",
|
|
32
32
|
"zod": "^4.4.3",
|
|
33
|
-
"@faapi/faapi": "^
|
|
33
|
+
"@faapi/faapi": "^4.0.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"typescript": {
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"scripts": {
|
|
64
64
|
"build": "tsup",
|
|
65
65
|
"test": "vitest run --passWithNoTests",
|
|
66
|
-
"typecheck": "tsc --noEmit"
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"typecheck:test": "tsc --noEmit -p tsconfig.test.json"
|
|
67
68
|
}
|
|
68
69
|
}
|