@faapi/agent 6.5.0 → 6.7.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 CHANGED
@@ -1044,6 +1044,42 @@ declare class Agent {
1044
1044
  private executeSubAgent;
1045
1045
  }
1046
1046
 
1047
+ /**
1048
+ * tool schema 解析工厂——把 tool 的 zod.js 解析为 AgentDeps.resolveToolSchema 契约值
1049
+ *
1050
+ * `AgentDeps.resolveToolSchema` 要求返回 [ToolSchemaResolution](./agent.md)
1051
+ * (`{ jsonSchema, validate }`),而 faapi 核心公开导出的
1052
+ * [loadToolSchema](../../faapi/src/loader/loadToolSchema.md) 返回的是
1053
+ * `{ schema, schemaName }` 原始 zod 模块——两者之间的装配(`z.toJSONSchema`
1054
+ * + `safeParse`)集中在本模块,经 `createToolSchemaResolver` 公开导出,
1055
+ * 插件 setup 与任务内组装 agent 共用同一实现,业务方无需镜像框架内部逻辑。
1056
+ *
1057
+ * 详见 [toolSchemaResolver.md](./toolSchemaResolver.md)。
1058
+ */
1059
+
1060
+ /**
1061
+ * 创建带 mtime 缓存的 tool schema 解析器
1062
+ *
1063
+ * 返回的函数满足 `AgentDeps.resolveToolSchema` 签名,供两处共用:
1064
+ * - `@faapi/agent` 插件 setup(传 `ctx.rootDir`,root + sub-agent 共享同一闭包缓存)
1065
+ * - 任务内组装 [Agent](./agent.md)(`TaskContext` 无 rootDir,缺省 `process.cwd()`——
1066
+ * faapi 服务进程 cwd 即项目根;建议任务文件模块级创建一次)
1067
+ *
1068
+ * **缓存语义**(闭包级 `Map<key, { mtimeMs, resolution }>`):
1069
+ * - 缓存键 `zodPath#inputTypeName`,每次查找 `statSync` 一次做 mtime 自校验——
1070
+ * mtime 变化即重新解析(dev reloadTools 重生成 zod.js 后自愈,prod 产物固化永远命中)
1071
+ * - in-flight Promise 直接入缓存:同一 tool 的并发调用共享同一次解析
1072
+ * - 每次 `createToolSchemaResolver` 调用返回独立缓存的 resolver
1073
+ *
1074
+ * zod.js 缺失 / tool 无 `inputTypeName` 时解析结果为 `undefined`——agent 用
1075
+ * 自由 schema `{ type: 'object' }`,LLM 自由传参。
1076
+ *
1077
+ * @param options.rootDir 项目根目录(缺省 `process.cwd()`)
1078
+ */
1079
+ declare function createToolSchemaResolver(options?: {
1080
+ rootDir?: string;
1081
+ }): (tool: ToolMetadata) => Promise<ToolSchemaResolution | undefined>;
1082
+
1047
1083
  /**
1048
1084
  * @faapi/agent faapi 插件——注册 agent handle 工厂,让 handler 的 `agent` 参数注入可用的 Agent 实例
1049
1085
  *
@@ -1071,7 +1107,8 @@ declare class Agent {
1071
1107
  * 1. 遍历 `config.agent.llms`(可选)→ 每项调 `createProvider` → `Map<providerKey, LLMProvider>`
1072
1108
  * 2. 读 `config.agent.maxTurns` / `maxAgentDepth`
1073
1109
  * 3. 从 `@faapi/faapi` import 注册表/加载器访问器(getAgent / getTool / resolveAgentTools /
1074
- * resolveSubAgents / loadAgentModule / loadToolModule
1110
+ * resolveSubAgents / loadAgentModule / loadToolModule),tool schema 解析用
1111
+ * [createToolSchemaResolver](./toolSchemaResolver.md)(setup 闭包级缓存)
1075
1112
  * 4. `registerAgentHandleFactory` 注册工厂——每次请求时构造 [Agent](./agent.md) 实例注入到
1076
1113
  * handler 的 `agent` 参数
1077
1114
  *
@@ -1092,4 +1129,4 @@ declare class Agent {
1092
1129
  */
1093
1130
  declare const agentPlugin: FaapiPlugin;
1094
1131
 
1095
- export { Agent, AgentAbortError, type AgentDeps, AgentError, type AgentHandle, AgentRecursionError, type AgentRunOptions, type AgentRuntimeConfig, type AgentTrace, type AgentTraceEvent, type LLMCompleteRequest, type LLMMessage, type LLMProvider, LLMProviderError, type LLMResponse, type LLMStopReason, type LLMStreamChunk, type LLMToolCall, type LLMToolDefinition, type LLMUsage, type LlmCallEvent, type ReactLoopConfig, ReactLoopError, type ReactLoopResult, type ReactLoopStreamChunk, type SubAgentCallEvent, type ToolCallEvent, type ToolExecutor, type ToolSchemaResolution, type TracingToolResult, createOpenAIProvider, createProvider, agentPlugin as default, isTracingToolResult, reactLoop, reactLoopStream };
1132
+ export { Agent, AgentAbortError, type AgentDeps, AgentError, type AgentHandle, AgentRecursionError, type AgentRunOptions, type AgentRuntimeConfig, type AgentTrace, type AgentTraceEvent, type LLMCompleteRequest, type LLMMessage, type LLMProvider, LLMProviderError, type LLMResponse, type LLMStopReason, type LLMStreamChunk, type LLMToolCall, type LLMToolDefinition, type LLMUsage, type LlmCallEvent, type ReactLoopConfig, ReactLoopError, type ReactLoopResult, type ReactLoopStreamChunk, type SubAgentCallEvent, type ToolCallEvent, type ToolExecutor, type ToolSchemaResolution, type TracingToolResult, createOpenAIProvider, createProvider, createToolSchemaResolver, agentPlugin as default, isTracingToolResult, reactLoop, reactLoopStream };
package/dist/index.js CHANGED
@@ -1359,15 +1359,10 @@ var Agent = class _Agent {
1359
1359
  }
1360
1360
  };
1361
1361
 
1362
- // src/plugin.ts
1363
- import {
1364
- loadAgentModule,
1365
- loadToolModule,
1366
- loadToolSchema,
1367
- getToolSchemaPath
1368
- } from "@faapi/faapi";
1362
+ // src/toolSchemaResolver.ts
1369
1363
  import { statSync } from "fs";
1370
1364
  import { z } from "zod";
1365
+ import { loadToolSchema, getToolSchemaPath } from "@faapi/faapi";
1371
1366
  async function resolveToolSchemaImpl(tool, rootDir) {
1372
1367
  const schemaMod = await loadToolSchema(tool, rootDir);
1373
1368
  if (!schemaMod) return void 0;
@@ -1383,6 +1378,32 @@ async function resolveToolSchemaImpl(tool, rootDir) {
1383
1378
  }
1384
1379
  };
1385
1380
  }
1381
+ function createToolSchemaResolver(options) {
1382
+ const rootDir = options?.rootDir ?? process.cwd();
1383
+ const schemaCache = /* @__PURE__ */ new Map();
1384
+ return (tool) => {
1385
+ const zodPath = getToolSchemaPath(tool, rootDir);
1386
+ const key = `${zodPath}#${tool.inputTypeName ?? ""}`;
1387
+ let mtimeMs = -1;
1388
+ try {
1389
+ mtimeMs = statSync(zodPath).mtimeMs;
1390
+ } catch {
1391
+ }
1392
+ const hit = schemaCache.get(key);
1393
+ if (hit && hit.mtimeMs === mtimeMs) {
1394
+ return hit.resolution;
1395
+ }
1396
+ const resolution = resolveToolSchemaImpl(tool, rootDir);
1397
+ schemaCache.set(key, { mtimeMs, resolution });
1398
+ return resolution;
1399
+ };
1400
+ }
1401
+
1402
+ // src/plugin.ts
1403
+ import {
1404
+ loadAgentModule,
1405
+ loadToolModule
1406
+ } from "@faapi/faapi";
1386
1407
  function readAgentConfig(ctx) {
1387
1408
  const raw = ctx.config?.agent;
1388
1409
  if (raw === void 0 || raw === null) return void 0;
@@ -1418,23 +1439,7 @@ var agentPlugin = {
1418
1439
  filterTools: agentConfig?.filterTools
1419
1440
  };
1420
1441
  const rootDir = ctx.rootDir;
1421
- const schemaCache = /* @__PURE__ */ new Map();
1422
- const resolveToolSchema = (tool) => {
1423
- const zodPath = getToolSchemaPath(tool, rootDir);
1424
- const key = `${zodPath}#${tool.inputTypeName ?? ""}`;
1425
- let mtimeMs = -1;
1426
- try {
1427
- mtimeMs = statSync(zodPath).mtimeMs;
1428
- } catch {
1429
- }
1430
- const hit = schemaCache.get(key);
1431
- if (hit && hit.mtimeMs === mtimeMs) {
1432
- return hit.resolution;
1433
- }
1434
- const resolution = resolveToolSchemaImpl(tool, rootDir);
1435
- schemaCache.set(key, { mtimeMs, resolution });
1436
- return resolution;
1437
- };
1442
+ const resolveToolSchema = createToolSchemaResolver({ rootDir });
1438
1443
  ctx.registries.agentHandle.register((ctx2) => {
1439
1444
  return new Agent({
1440
1445
  providers,
@@ -1473,6 +1478,7 @@ export {
1473
1478
  ReactLoopError,
1474
1479
  createOpenAIProvider,
1475
1480
  createProvider,
1481
+ createToolSchemaResolver,
1476
1482
  plugin_default as default,
1477
1483
  isTracingToolResult,
1478
1484
  reactLoop,
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 { AgentAbortError } from '../provider';\nimport type {\n LLMCompleteRequest,\n LLMMessage,\n LLMProvider,\n LLMResponse,\n LLMStopReason,\n LLMStreamChunk,\n LLMToolCall,\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 /** thinking 模型推理内容(DeepSeek / Qwen 线格式) */\n reasoning_content?: string | null;\n /** thinking 模型推理内容(OpenRouter 线格式) */\n reasoning?: string | null;\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 /** thinking 模型推理增量(DeepSeek / Qwen 线格式) */\n reasoning_content?: string | null;\n /** thinking 模型推理增量(OpenRouter 线格式) */\n reasoning?: string | null;\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 // 规范形即 OpenAI 形状:messages 恒等透传(剥离框架扩展字段 reasoning_content,\n // 推理内容是解析产物不回传——DeepSeek 多轮回传直接 400,OpenAI 等拒绝未知字段)\n messages: stripReasoningFromMessages(request.messages),\n };\n\n if (request.tools && request.tools.length > 0) {\n body.tools = request.tools;\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 = normalizeToolCalls(msg.tool_calls, bodyText, response.status);\n\n // assistant 消息按规范形构造(OpenAI 形状,tool_calls 原样保留字符串参数)\n const message: LLMMessage = { role: 'assistant', content };\n if (toolCalls) message.tool_calls = toolCalls;\n // thinking 模型推理内容(解析产物,不回传——发送侧由 stripReasoningFromMessages 剥离)\n const reasoning = extractReasoning(msg);\n if (reasoning) message.reasoning_content = reasoning;\n\n return {\n message,\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 // 推理内容增量(thinking 模型,先于 content 输出)\n const reasoning = extractReasoning(delta);\n if (reasoning) {\n yield { deltaReasoning: reasoning };\n }\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/**\n * 提取 thinking 模型的推理内容(规范形映射)\n *\n * 兼容两种线格式:`reasoning_content`(DeepSeek / Qwen 事实标准)优先,\n * 缺失时读 `reasoning`(OpenRouter 形状)。两者皆无/为空返回 `undefined`。\n */\nfunction extractReasoning(source: {\n reasoning_content?: string | null;\n reasoning?: string | null;\n}): string | undefined {\n if (typeof source.reasoning_content === 'string' && source.reasoning_content.length > 0) {\n return source.reasoning_content;\n }\n if (typeof source.reasoning === 'string' && source.reasoning.length > 0) {\n return source.reasoning;\n }\n return undefined;\n}\n\n/**\n * 剥离 messages 上的 `reasoning_content`(请求侧不回传,线格式合法性由 provider 边界兜底)\n *\n * `reasoning_content` 是响应解析产物:DeepSeek 明确要求多轮对话不回传(回传 400),\n * OpenAI 等拒绝未知字段。任一消息都不带该字段时**原数组原样返回**(恒等透传零拷贝);\n * 带该字段的消息浅拷贝副本剥离,其余消息保持原引用。业务方经续跑历史 / 自定义\n * messages 传入的带推理内容历史同样被剥离。\n */\nfunction stripReasoningFromMessages(messages: LLMMessage[]): LLMMessage[] {\n if (!messages.some((m) => m.reasoning_content !== undefined)) {\n return messages;\n }\n return messages.map((m) => {\n if (m.reasoning_content === undefined) return m;\n const { reasoning_content: _stripped, ...rest } = m;\n return rest;\n });\n}\n\n/**\n * OpenAI tool_calls 数组 → 规范形 LLMToolCall[]\n *\n * 补全缺失的 id/name 默认值并**校验 arguments 可解析**(不合法抛\n * `LLMProviderError`,fail-fast 在 provider 边界),`arguments` 保持线格式\n * JSON 字符串——解析边界收敛在 reactLoop(执行前 parse)。\n */\nfunction normalizeToolCalls(\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 try {\n 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 type: 'function',\n function: { name: tc?.function?.name ?? '', arguments: argsStr },\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(snake_case,缺省补 0) */\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 prompt_tokens: u.prompt_tokens ?? 0,\n completion_tokens: u.completion_tokens ?? 0,\n total_tokens: 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[](arguments 保持 JSON 字符串,校验可解析)+ 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 const argsStr = acc.argsString || '{}';\n try {\n JSON.parse(argsStr);\n } catch {\n const excerpt = argsStr.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 type: 'function',\n function: { name: acc.name, arguments: argsStr },\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 * 对话消息(OpenAI chat completions 规范形)\n *\n * 四种 role 与 OpenAI chat completions 一致:\n * - `system` —— 系统提示词(agent 的 systemPrompt)\n * - `user` —— 用户输入\n * - `assistant` —— LLM 回复(可能含 tool_calls)\n * - `tool` —— tool 执行结果(需带 tool_call_id 标识对应哪个 tool_call)\n *\n * 字段拼写与 OpenAI 线格式一致(`tool_calls` / `tool_call_id`),OpenAI provider\n * 对 messages 恒等透传,观测/存储/展示消费方按 OpenAI 形状处理即可。\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 tool_call_id?: string;\n /** role='assistant' 时:LLM 请求的 tool 调用(reactLoop 据此执行 tool) */\n tool_calls?: LLMToolCall[];\n /**\n * role='assistant' 时:thinking 模型的推理内容(DeepSeek 线格式字段名)\n *\n * provider 响应解析产物(非流式含完整内容),供业务方展示/审计;\n * **不回传 LLM API**——OpenAI provider 发送请求时剥离(DeepSeek 多轮回传\n * 推理内容直接 400,OpenAI 等拒绝未知字段),reactLoop 组装历史时同样剥离。\n * 兼容解析:线格式 `reasoning_content` 优先,缺失时读 `reasoning`(OpenRouter 形状)。\n */\n reasoning_content?: string;\n}\n\n/**\n * assistant 消息内的 tool 调用(OpenAI chat completions 规范形)\n *\n * 由 LLM 在 assistant 消息中返回。`function.arguments` 是线格式的 JSON **字符串**\n * (不预解析)——消息历史可原样透传与持久化;[reactLoop](./reactLoop.md) 在执行前\n * JSON.parse,tool 执行函数 / 鉴权钩子 / trace 事件拿到的都是已 parse 的对象。\n */\nexport interface LLMToolCall {\n /** tool call ID(provider 分配,用于匹配 tool 结果) */\n id: string;\n /** 固定 `'function'`(OpenAI 线格式) */\n type: 'function';\n function: {\n /** tool 名(匹配 LLMToolDefinition.function.name) */\n name: string;\n /** tool 参数(JSON 字符串,线格式原样) */\n arguments: string;\n };\n}\n\n/**\n * Tool 定义(OpenAI chat completions 规范形)\n *\n * 由 [reactLoop](./reactLoop.md) 从 [toolRegistry](../../faapi/src/injection/toolRegistry.md)\n * + [agentRegistry.resolveSubAgents](../../faapi/src/injection/agentRegistry.md) 组装:\n * - 常规 tool:`function.parameters` 来自 AST 提取的 zod schema(JSON Schema 形式)\n * - agent-as-tool:`function.parameters` 为自由 schema(agent 参数开放)\n */\nexport interface LLMToolDefinition {\n /** 固定 `'function'`(OpenAI 线格式) */\n type: 'function';\n function: {\n /** tool 名(如 `weather.getWeather` 或 `agent.researcher`) */\n name: string;\n /** tool 描述(对 LLM 可见,引导 LLM 选择调用) */\n description?: string;\n /** JSON Schema 对象(描述 tool 参数结构) */\n parameters?: Record<string, unknown>;\n };\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 *\n * `messages` 为中断时刻的部分对话历史(截至最后一个完整轮组),由\n * [reactLoop](./reactLoop.md) 在循环边界附加(provider 层不感知历史,恒为空数组)。\n * 业务方持久化后经 `config.messages` / `AgentRunOptions.messages` 从断点续跑,\n * 语义详见 [reactLoop.md](./reactLoop.md) 中断恢复章节。\n */\nexport class AgentAbortError extends Error {\n /** 中断时刻的部分对话历史(完整轮组快照,可直接用于续跑) */\n readonly messages: LLMMessage[];\n\n constructor(message = 'Agent execution aborted', messages: LLMMessage[] = []) {\n super(message);\n this.name = 'AgentAbortError';\n this.messages = messages;\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.tool_calls` 不为空时 stopReason 应为 `'tool_calls'`。\n */\nexport interface LLMResponse {\n /** assistant 消息(OpenAI 规范形,含 content + 可选 tool_calls) */\n message: LLMMessage;\n /** 停止原因(reactLoop 据此判断是否进入下一轮) */\n stopReason: LLMStopReason;\n /** token 用量(部分 provider 不返回) */\n usage?: LLMUsage;\n}\n\n/**\n * Token 用量(OpenAI chat completions 规范形)\n */\nexport interface LLMUsage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n}\n\n/**\n * stream 的单个 chunk\n *\n * provider 内部流抽象(非 OpenAI 分片 delta 线格式):\n * - 内容流:`deltaContent` 为增量 token\n * - 推理内容流(thinking 模型):`deltaReasoning` 为推理增量(与 `deltaContent` 同为\n * 增量语义,消费方按到达顺序各自拼接)\n * - tool 调用:累积完成后在最终 chunk 一并 emit `toolCalls`(OpenAI 规范形,\n * `function.arguments` 为 JSON 字符串)\n * - 结束:最终 chunk 含 `finishReason` + 可选 `usage`\n */\nexport interface LLMStreamChunk {\n /** 增量内容(streaming token) */\n deltaContent?: string;\n /** 推理内容增量(thinking 模型,如 DeepSeek `reasoning_content` / OpenRouter `reasoning`) */\n deltaReasoning?: 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 LLMResponse,\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 * 初始对话历史(续跑 / 多轮对话)\n *\n * 提供时以其为基础(历史应含 system):历史无 `system` 消息且配置了\n * `systemPrompt` 时自动在最前插入(agent 人格不因续跑丢失);`input` 非空时\n * 追加为新的 `user` 消息(多轮对话),为空时纯续跑。历史经 `Agent` 层结构校验\n * (assistant.tool_calls 与 tool 结果按 tool_call_id 配对完整)。\n * 续跑源见 [reactLoop.md](./reactLoop.md) 中断恢复章节。\n */\n messages?: LLMMessage[];\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 /**\n * 最终 assistant 的推理内容(thinking 模型,多轮时中间轮的推理不保留;\n * 无推理内容时不存在)。历史 messages 中的 assistant 消息已剥离推理内容,\n * 仅此字段与 trace 的 `llm_call.response.reasoning_content` 可读。\n */\n reasoning?: 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 * - `deltaReasoning` — LLM 推理内容增量(thinking 模型,含中间 tool 轮)\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 /** LLM 推理内容增量(thinking 模型,见 reactLoop.md thinking 章节) */\n deltaReasoning?: 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 / deltaReasoning / toolCall / toolResult / done 互斥,一个 chunk 至多一个字段。\n */\n traceEvent?: AgentTraceEvent;\n /** 循环结束 */\n done?: {\n content: string;\n /** 最终轮的完整推理内容(thinking 模型;无推理内容时不存在) */\n reasoning?: 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 /** 超限时的完整对话历史(业务方可提高 maxTurns 后经 `config.messages` 续跑,轮数重新计数) */\n readonly messages: LLMMessage[];\n\n constructor(message: string, maxTurns: number, messages: LLMMessage[] = []) {\n super(message);\n this.name = 'ReactLoopError';\n this.maxTurns = maxTurns;\n this.messages = messages;\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/**\n * 剥离 assistant 消息上的 `reasoning_content`(历史保持 OpenAI 线格式纯净)\n *\n * 推理内容只透出给调用方(`ReactLoopResult.reasoning` / `deltaReasoning` / trace 的\n * `llm_call.response`),不进入对话历史——历史是发给 LLM 的(DeepSeek 多轮回传推理\n * 内容直接 400)也是持久化 / 续跑 / `AgentAbortError.messages` / `ReactLoopError.messages`\n * 的来源。无该字段时原引用返回(零拷贝常态路径)。\n */\nfunction stripReasoning(message: LLMMessage): LLMMessage {\n if (message.reasoning_content === undefined) return message;\n const { reasoning_content: _stripped, ...rest } = message;\n return rest;\n}\n\n/** 累加 usage */\nfunction accumulateUsage(a: LLMUsage | undefined, b: LLMUsage): LLMUsage {\n if (!a) return { ...b };\n return {\n prompt_tokens: a.prompt_tokens + b.prompt_tokens,\n completion_tokens: a.completion_tokens + b.completion_tokens,\n total_tokens: a.total_tokens + b.total_tokens,\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/**\n * 解析 tool_call 的 `function.arguments`(OpenAI 线格式 JSON 字符串)为对象\n *\n * 解析边界收敛在此——tool 执行函数 / 鉴权钩子 / trace 事件拿到的都是已 parse 的对象。\n * 解析失败抛错,由调用方的 tool 错误路径 catch 后回传 LLM(LLM 可修正参数重试)。\n */\nfunction parseToolCallArguments(toolCall: LLMToolCall): Record<string, unknown> {\n const raw = toolCall.function.arguments;\n if (!raw) return {};\n return JSON.parse(raw) as Record<string, unknown>;\n}\n\n/**\n * 构造循环初始 messages(含续跑语义,见 reactLoop.md 中断恢复章节)\n *\n * - `config.messages` 提供(续跑 / 多轮对话):以其为基础——历史无 `system` 时\n * 自动前置 `systemPrompt`(agent 人格不因续跑丢失);非空 `input` 追加为新的\n * user 消息,空 input 纯续跑\n * - 未提供:`systemPrompt`(可选)+ user input(现状行为,input 为空时 user 内容为空串,\n * 输入守卫由 Agent 层负责)\n */\nfunction initLoopMessages(input: string | undefined, config: ReactLoopConfig): LLMMessage[] {\n if (config.messages?.length) {\n // 剥离业务方历史上的 reasoning_content(推理内容不进对话历史,见 stripReasoning)\n const messages = config.messages.map(stripReasoning);\n if (config.systemPrompt && !messages.some((m) => m.role === 'system')) {\n messages.unshift({ role: 'system', content: config.systemPrompt });\n }\n if (input) {\n messages.push({ role: 'user', content: input });\n }\n return messages;\n }\n return buildInitialMessages(input ?? '', config.systemPrompt);\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.tool_calls) {\n chars += JSON.stringify(message.tool_calls).length;\n }\n if (message.tool_call_id) {\n chars += message.tool_call_id.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 用户输入(续跑场景可为空,历史经 `config.messages` 提供)\n * @param config 循环配置\n * @returns 最终结果(content + reasoning + messages + turns + stopReason + usage)\n * @throws {ReactLoopError} 超出 maxTurns(`error.messages` 携带完整历史,可续跑)\n * @throws {AgentAbortError} 中断(`error.messages` 携带断点历史,可续跑)\n * @throws {Error} provider.complete 抛错时立即传播\n */\nexport async function reactLoop(\n input: string | undefined,\n config: ReactLoopConfig,\n): Promise<ReactLoopResult> {\n const enableTracing = config.enableTracing ?? false;\n const messages = initLoopMessages(input, config);\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n const extras = buildRequestExtras(config);\n let totalUsage: LLMUsage | undefined;\n /** 最终轮 assistant 的推理内容(thinking 模型;每轮覆盖,循环结束时即最终轮值) */\n let finalReasoning: string | 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(undefined, messages);\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 // 执行中取消由 provider 请求中断传播——附上断点历史重抛(不含未完成的 assistant 消息,\n // 轮组原子性见 reactLoop.md 中断恢复章节)\n let response: LLMResponse;\n try {\n response = await config.provider.complete({\n messages: [...outgoing],\n ...extras,\n signal: config.signal,\n });\n } catch (err) {\n if (err instanceof AgentAbortError) {\n throw new AgentAbortError(err.message, messages);\n }\n throw err;\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 // 原始 assistant 消息(含 reasoning_content)——trace 保留完整 LLM 返回,\n // 与历史剥离策略互补(见 trace.md「llm_call.response 与 thinking」)\n response: response.message,\n stopReason: response.stopReason,\n usage: response.usage,\n });\n }\n\n // 把 assistant 消息加入历史(剥离 reasoning_content——推理内容不进对话历史,\n // 见 stripReasoning 与 reactLoop.md thinking 章节)\n finalReasoning = response.message.reasoning_content;\n messages.push(stripReasoning(response.message));\n\n // 非 tool_calls → 循环结束\n if (response.stopReason !== 'tool_calls' || !response.message.tool_calls) {\n const traceEndedAt = enableTracing ? nowMs() : 0;\n const result: ReactLoopResult = {\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 if (finalReasoning !== undefined) {\n result.reasoning = finalReasoning;\n }\n return result;\n }\n\n // 并行执行同轮全部 tool_call——多个独立 tool 的总耗时从「各 tool 之和」\n // 降为「最慢一个」。每个 toolCall 独立 try/catch(单个失败不影响其余),\n // 结果按下方的 tool_calls 声明顺序回传(与完成顺序无关,保证 tool 配对语义)。\n // 注:beforeToolCall/afterToolCall 钩子会并发触发,业务方钩子不应依赖调用顺序。\n // 流式路径(reactLoopStream)保持串行——yield 顺序受消费端约束。\n // arguments 解析边界在此收拢:线格式 JSON 字符串在此 parse,执行函数 /\n // 鉴权钩子 / trace 事件拿到的都是已 parse 的对象;解析失败走 tool 错误路径回传。\n const settled = await Promise.all(\n response.message.tool_calls.map(async (toolCall) => {\n const toolStartedAt = enableTracing ? nowMs() : 0;\n const toolName = toolCall.function.name;\n let args: Record<string, unknown> = {};\n let resultStr: string;\n let rawResult: unknown | TracingToolResult;\n let toolErr: unknown;\n let hasError = false;\n try {\n args = parseToolCallArguments(toolCall);\n rawResult = await config.executeTool(toolName, args);\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 toolName,\n args,\n toolStartedAt,\n toolEndedAt,\n resultStr,\n rawResult,\n toolErr,\n hasError,\n };\n }),\n );\n\n for (const {\n toolCall,\n toolName,\n args,\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(toolName),\n input: JSON.stringify(args),\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: toolName,\n arguments: args,\n result: resultStr,\n error: hasError ? stringifyError(toolErr) : undefined,\n });\n }\n }\n\n messages.push({\n role: 'tool',\n content: resultStr,\n tool_call_id: 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 messages,\n );\n}\n\n// ─── reactLoopStream(流式)──────────────────────────\n\n/**\n * 执行 ReAct 循环(流式)\n *\n * 使用 `provider.stream()` 异步迭代 chunks,yield `deltaContent` + `deltaReasoning` + `toolCall` + `toolResult` + `done`。\n *\n * @param input 用户输入(续跑场景可为空,历史经 `config.messages` 提供)\n * @param config 循环配置\n * @yields {ReactLoopStreamChunk} 流式 chunk\n * @throws {ReactLoopError} 超出 maxTurns(`error.messages` 携带完整历史,可续跑)\n * @throws {AgentAbortError} 中断(`error.messages` 携带断点历史,可续跑)\n * @throws {Error} provider.stream 抛错时立即传播\n */\nexport async function* reactLoopStream(\n input: string | undefined,\n config: ReactLoopConfig,\n): AsyncIterable<ReactLoopStreamChunk> {\n const enableTracing = config.enableTracing ?? false;\n const messages = initLoopMessages(input, config);\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(undefined, messages);\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 turnReasoning = '';\n let toolCalls: LLMToolCall[] | undefined;\n let finishReason: LLMStopReason | undefined;\n let turnUsage: LLMUsage | undefined;\n\n // 执行中取消由 provider 流中断传播——附上断点历史重抛。当前轮已 yield 的部分\n // deltaContent 属于未完成轮组,不入历史(轮组原子性见 reactLoop.md 中断恢复章节)\n try {\n for await (const chunk of config.provider.stream({\n messages: [...outgoing],\n ...extras,\n signal: config.signal,\n })) {\n // 推理内容增量(thinking 模型,中间 tool 轮同样透出——业务方可完整展示思考过程)\n if (typeof chunk.deltaReasoning === 'string' && chunk.deltaReasoning.length > 0) {\n turnReasoning += chunk.deltaReasoning;\n yield { deltaReasoning: chunk.deltaReasoning };\n }\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 } catch (err) {\n if (err instanceof AgentAbortError) {\n throw new AgentAbortError(err.message, messages);\n }\n throw err;\n }\n\n // 把 assistant 消息加入历史(含 tool_calls,供下一轮 LLM 上下文;规范形,\n // function.arguments 保持线格式 JSON 字符串;本就是新构造对象,天然无推理内容)\n const assistantMessage: LLMMessage = {\n role: 'assistant',\n content: turnContent,\n };\n if (toolCalls) {\n assistantMessage.tool_calls = toolCalls;\n }\n messages.push(assistantMessage);\n\n if (enableTracing) {\n const llmEndedAt = nowMs();\n // trace 保留该轮完整 LLM 返回(含累积的推理内容),与历史剥离互补;\n // 无推理内容时不拷贝(零开销常态路径)\n const tracedResponse = turnReasoning\n ? { ...assistantMessage, reasoning_content: turnReasoning }\n : assistantMessage;\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: tracedResponse,\n stopReason: finishReason ?? 'other',\n usage: turnUsage,\n },\n };\n }\n\n // 非 tool_calls → 循环结束\n if (finishReason !== 'tool_calls' || !toolCalls) {\n const donePayload: NonNullable<ReactLoopStreamChunk['done']> = {\n content: turnContent,\n turns,\n stopReason: finishReason ?? 'other',\n usage: totalUsage,\n };\n if (turnReasoning) {\n donePayload.reasoning = turnReasoning;\n }\n yield { done: donePayload };\n return;\n }\n\n // 执行每个 tool call(arguments 解析边界与非流式路径一致:先 parse,\n // 解析失败走 tool 错误路径回传 LLM)\n for (const toolCall of toolCalls) {\n const toolName = toolCall.function.name;\n const toolStartedAt = enableTracing ? nowMs() : 0;\n let args: Record<string, unknown> = {};\n let resultStr: string;\n let rawResult: unknown | TracingToolResult;\n let toolErr: unknown;\n let hasError = false;\n try {\n args = parseToolCallArguments(toolCall);\n yield { toolCall: { name: toolName, arguments: args } };\n rawResult = await config.executeTool(toolName, args);\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: toolName, 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(toolName),\n input: JSON.stringify(args),\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: toolName,\n arguments: args,\n result: resultStr,\n error: hasError ? stringifyError(toolErr) : undefined,\n },\n };\n }\n }\n\n messages.push({\n role: 'tool',\n content: resultStr,\n tool_call_id: 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 messages,\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 { createProvider } from './provider';\nimport type { LLMMessage, 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/** 合法消息 role(与 LLMMessage 的 role 联合一致,运行时校验反序列化历史用) */\nconst VALID_MESSAGE_ROLES = new Set(['system', 'user', 'assistant', 'tool']);\n\n/**\n * 判断是否为 LLMProvider 实例(有 `complete` / `stream` 方法)\n *\n * 用于 `options.provider` 的运行时形状判别——`LLMProvider` 实例直接使用,\n * `LlmConfig` 配置对象走 `createProvider` 现场创建。\n */\nfunction isProviderInstance(value: unknown): value is LLMProvider {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as LLMProvider).complete === 'function' &&\n typeof (value as LLMProvider).stream === 'function'\n );\n}\n\n/**\n * 判断是否为 LlmConfig 配置对象(含字符串 `provider` 字段)\n */\nfunction isProviderConfig(value: unknown): value is LlmConfig {\n return (\n typeof value === 'object' && value !== null && typeof (value as LlmConfig).provider === 'string'\n );\n}\n\n/**\n * 物化外部 provider(`options.provider` → LLMProvider 实例)\n *\n * - `LLMProvider` 实例 → 直接使用(自定义内部模型网关等)\n * - `LlmConfig` 配置对象 → `createProvider` 现场创建(浅拷贝 + `models` 兜底 `{}`,\n * 不改调用方对象;每次调用独立创建,无共享状态,不进 providers Map)\n * - 两者皆非 → 抛 `AgentError`(不降级猜测)\n *\n * @throws {AgentError} 形式非法(既非 LlmConfig 也非 LLMProvider)\n */\nfunction materializeProvider(external: LlmConfig | LLMProvider): LLMProvider {\n if (isProviderInstance(external)) return external;\n if (isProviderConfig(external)) {\n return createProvider({ ...external, models: external.models ?? {} });\n }\n throw new AgentError(\n 'options.provider must be an LlmConfig object (with a string \"provider\" field) or an LLMProvider instance (with complete/stream methods)',\n );\n}\n\n/**\n * 校验续跑历史结构(见 reactLoop.md 中断恢复章节)\n *\n * `AgentRunOptions.messages` 是业务方持久化后回传的历史,最常见的损坏是截断在\n * 轮组中间(assistant.toolCalls 缺 tool 结果)或反序列化出非法 role——直接发给\n * LLM API 只会得到模糊的 400。此处早失败(抛 `AgentError`,不发起 LLM 请求)。\n *\n * 校验规则:\n * - role 必须是 system / user / assistant / tool 之一\n * - assistant 消息带 `toolCalls` 时,其后必须紧跟对应数量的 tool 结果消息\n * (按 `toolCallId` 配对,在任何非 tool 消息之前)\n *\n * @throws {AgentError} 历史结构非法(消息含索引与 toolCallId,可定位损坏点)\n */\nfunction validateResumeHistory(messages: LLMMessage[]): void {\n messages.forEach((message, index) => {\n if (!VALID_MESSAGE_ROLES.has(message.role)) {\n throw new AgentError(\n `Invalid resume history at messages[${index}]: unknown role \"${String(message.role)}\"`,\n );\n }\n if (message.role === 'assistant' && message.tool_calls?.length) {\n const missing = new Set(message.tool_calls.map((call) => call.id));\n for (let j = index + 1; j < messages.length && messages[j]!.role === 'tool'; j++) {\n missing.delete(messages[j]!.tool_call_id ?? '');\n }\n if (missing.size > 0) {\n throw new AgentError(\n `Invalid resume history at messages[${index}]: assistant tool call(s) [${Array.from(missing).join(', ')}] have no matching tool result (history truncated mid-turn — persist the full turn group from AgentAbortError.messages / ReactLoopError.messages)`,\n );\n }\n }\n });\n}\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 * 本次调用的解析结果([buildLoopConfig](#buildLoopConfig) 解析后经闭包传给 executeTool)\n *\n * - `agentName`——本次调用的有效 agent 名(执行白名单按它的声明集合校验)\n * - `enableTracing`——sub-agent 调用是否包装 TracingToolResult\n * - `provider` / `model`——解析出的 LLM 入口,sub-agent 递归继承(sub 元数据\n * 声明 `model` 时优先用自身的)\n */\ninterface AgentCallContext {\n agentName: string;\n enableTracing: boolean;\n provider: LLMProvider;\n model: string | undefined;\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;未配置时为空 Map) */\n providers: Map<string, LLMProvider>;\n /** LLM provider 配置映射(含 models,用于 options.model key 解析;llms 未配置时为空对象) */\n llms: Record<string, LlmConfig>;\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 + 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 返回后填充 `options.agent`,让顶层 trace 标识\"是哪个 agent 跑的\"。\n *\n * @param input 用户输入(可选——续跑场景不传新输入;input 与 `options.messages`\n * 都为空时抛 `AgentError`)\n * @param options 本次调用配置——`agent`(agent 名,必须显式传,无默认 agent)/\n * provider(外部 provider)/ model(字符串 key)/ temperature /\n * maxTokens / messages / enableTracing\n * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))\n * @returns 最终结果(content + messages + turns + stopReason + usage + trace?)\n * @throws {AgentError} 未传 options.agent;agent 未注册;input 与 messages 都为空;\n * 续跑历史结构非法;provider/model 无法解析\n * @throws {ReactLoopError} 超出 maxTurns(`error.messages` 携带完整历史,可续跑)\n * @throws {AgentAbortError} 中断(`error.messages` 携带断点历史,可续跑)\n * @throws {Error} provider.complete 抛错时立即传播\n */\n async run(input?: string, options?: AgentRunOptions): Promise<ReactLoopResult> {\n const config = await this.buildLoopConfig(input, 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 ?? '';\n }\n return result;\n }\n\n /**\n * 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)\n *\n * @param input 用户输入(可选——续跑场景不传新输入;input 与 `options.messages`\n * 都为空时抛 `AgentError`)\n * @param options 本次调用配置——`agent`(必须显式传)/ provider / model /\n * temperature / maxTokens / messages\n * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))\n * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)\n * @throws {AgentError} 未传 options.agent;agent 未注册;input 与 messages 都为空;\n * 续跑历史结构非法;provider/model 无法解析\n * @throws {ReactLoopError} 超出 maxTurns(`error.messages` 携带完整历史,可续跑)\n * @throws {AgentAbortError} 中断(`error.messages` 携带断点历史,可续跑)\n * @throws {Error} provider.stream 抛错时立即传播\n */\n async *stream(input?: string, options?: AgentRunOptions): AsyncIterable<ReactLoopStreamChunk> {\n const config = await this.buildLoopConfig(input, options);\n yield* reactLoopStream(input, config);\n }\n\n /**\n * 把指定 agent 包装为 `AgentToolDescriptor` 供 LLM 当 tool 调用\n *\n * 与 [agentRegistry.asTool](../../faapi/src/injection/agentRegistry.md) 同构——\n * Agent 类自带此方法便于在注入器场景直接调用(不必再过注册表)。\n *\n * @param name agent 名(显式指定——无默认 agent)\n * @returns `AgentToolDescriptor` 或 `undefined`(agent 未注册)\n */\n asTool(name: string): AgentToolDescriptor | undefined {\n const meta = this.deps.getAgent(name);\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`(必须显式传——无默认 agent,不传抛 AgentError)\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\n *\n * `options.provider`(外部 provider)存在时由 {@link resolveExternalProvider} 物化,\n * 优先级最高——`options.model` 变为原始 model 名原样透传(不解析 llms key)。\n * 否则 `options.model` 是字符串 key,由 {@link resolveModelKey} 解析为 provider + model\n * (支持 llms key 精确匹配 / `provider/model` 一体化 / 纯 model 名模糊匹配;\n * 未传 `options.model` 时用 agent 元数据 `config.model` 作为缺省 key)。\n * 详见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。\n *\n * **输入守卫**(续跑入口,见 [reactLoop.md](./reactLoop.md) 中断恢复章节):\n * `input` 与 `options.messages` 都为空时抛 `AgentError`(不发送空请求);\n * `options.messages` 提供时先经 `validateResumeHistory` 结构校验,非法抛\n * `AgentError`,不发起 LLM 请求。\n */\n private async buildLoopConfig(\n input: string | undefined,\n options?: AgentRunOptions,\n ): Promise<ReactLoopConfig> {\n // 输入守卫:全新对话必须有 input,续跑必须有 messages(空 input + messages = 纯续跑)\n if (!input && !options?.messages?.length) {\n throw new AgentError(\n 'agent.run/stream requires non-empty input, or options.messages to resume from (AgentAbortError.messages / ReactLoopError.messages / previous result.messages)',\n );\n }\n if (options?.messages?.length) {\n validateResumeHistory(options.messages);\n }\n\n // 无默认 agent——options.agent 必须显式传\n const agentName = options?.agent;\n if (!agentName) {\n throw new AgentError(\n 'agent.run/stream requires options.agent (no default agent) — pass { agent: \"name\" } to specify which agent to run',\n );\n }\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 // 解析 provider + model:options.provider(外部 provider)优先级最高,存在时跳过\n // options.model 的 llms key 解析(model 原样透传);否则按字符串 key 规则解析\n const { provider, model } =\n options?.provider !== undefined\n ? this.resolveExternalProvider(options.provider, options?.model)\n : 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 // 本次调用的解析结果——executeTool / executeSubAgent 复用(白名单校验用 agentName,\n // sub-agent 递归继承 provider/model)\n const callCtx = { agentName, enableTracing, provider, model };\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 messages: options?.messages,\n enableTracing,\n executeTool: async (name, args) => this.executeTool(name, args, callCtx),\n };\n }\n\n /**\n * 解析外部 provider(`options.provider`)→ provider + model\n *\n * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.provider` 外部 provider」章节:\n * - `LLMProvider` 实例 → 直接使用,`modelKey` 原样透传(可为 `undefined`,自定义 provider 自决)\n * - `LlmConfig` 配置对象 → `createProvider` 现场创建,`modelKey` 原样透传;\n * 缺省回落该 config `models` 第一个 key,两者皆无抛 `AgentError`(早失败,不发请求)\n * - `modelKey` 不做 llms key 解析、不拆 `/`(支持 OpenRouter 等带斜杠的 model id)\n *\n * 仅本次调用生效:不进 providers Map、sub-agent 递归不继承(executeSubAgent 构造\n * subDeps 时不携带 options,sub-agent 走默认解析链路)。\n *\n * @throws {AgentError} provider 形式非法;LlmConfig 形式下 model 缺失\n */\n private resolveExternalProvider(\n external: LlmConfig | LLMProvider,\n modelKey: string | undefined,\n ): { provider: LLMProvider; model: string | undefined } {\n const provider = materializeProvider(external);\n if (isProviderInstance(external)) {\n return { provider, model: modelKey };\n }\n const firstModel = Object.keys(external.models ?? {})[0];\n const model = modelKey ?? firstModel;\n if (model === undefined) {\n throw new AgentError(\n 'External provider requires a model: pass options.model or declare models in the provider config',\n );\n }\n return { provider, model };\n }\n\n /**\n * 解析 `options.model` 字符串 key → provider + model\n *\n * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。\n * 无默认 provider——`key` 未传时用 agent 元数据 `config.model` 作为缺省 key;\n * 两者皆无抛 `AgentError`(要求调用方传 `options.model` 或 `options.provider`)。\n * 1. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key\n * (该 provider 未声明 `models` 时回落 `meta.model`)\n * 2. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model\n * (要求该 model 在 `deps.llms[provider].models` 里)\n * 3. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找\n * - 唯一 → 该 provider + 该 model\n * - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)\n * - 无 → 抛 `AgentError`\n *\n * @throws {AgentError} key 与 `meta.model` 均缺省;key 解析失败(provider/model\n * 不存在或歧义)\n */\n private resolveModelKey(\n key: string | undefined,\n meta: AgentCore,\n ): { provider: LLMProvider; model: string | undefined } {\n // 无默认 provider——未传 options.model 时用 agent 元数据 model 作为缺省 key\n const effectiveKey = key ?? meta.model;\n if (effectiveKey === undefined) {\n throw new AgentError(\n 'No LLM provider resolved: pass options.model (a provider key / \"provider/model\" / model name from config.agent.llms), options.provider (external provider), or declare model in the agent config',\n );\n }\n\n // 规则 1:精确匹配 providers key(如 'openai')\n const byProviderKey = this.deps.providers.get(effectiveKey);\n if (byProviderKey) {\n const llmConfig = this.deps.llms[effectiveKey];\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 (effectiveKey.includes('/')) {\n const slashIdx = effectiveKey.indexOf('/');\n const providerName = effectiveKey.slice(0, slashIdx);\n const modelName = effectiveKey.slice(slashIdx + 1);\n const provider = this.deps.providers.get(providerName);\n if (!provider) {\n throw new AgentError(\n `Unknown provider \"${providerName}\" in model key \"${effectiveKey}\". Declare it in config.agent.llms, or pass options.provider to use an external provider.`,\n );\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[effectiveKey]) {\n matches.push({ provider, providerName });\n }\n }\n if (matches.length === 1) {\n return { provider: matches[0]!.provider, model: effectiveKey };\n }\n if (matches.length > 1) {\n throw new AgentError(\n `Model \"${effectiveKey}\" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(', ')}). Use \"provider/model\" to disambiguate.`,\n );\n }\n throw new AgentError(\n `Model \"${effectiveKey}\" not found in any provider. Declare it in config.agent.llms.*.models, or pass options.provider to use an external provider.`,\n );\n }\n\n /**\n * 组装 LLM 可见 tool 列表(OpenAI chat completions 规范形)\n *\n * 合并两个来源(按 `function.name` 去重,先入者保留):\n * 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用\n * 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`\n *\n * 每个常规 tool 的 `function.parameters`:\n * - `resolveToolSchema` 提供 → 用其 `jsonSchema`\n * - 未提供 / tool 无 `inputTypeName` → 自由 schema `{ type: 'object' }`\n *\n * sub-agent 的 `function.parameters` 始终为 `{ 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 type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: schemaRes?.jsonSchema ?? { type: 'object' },\n },\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 type: 'function',\n function: {\n name,\n description: subAgent.description,\n parameters: { type: 'object' },\n },\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 * `callCtx` 由 [buildLoopConfig](#buildLoopConfig) 闭包捕获传入——本次调用的有效\n * agent 名(白名单校验)、enableTracing(sub-agent tracing 包装)与解析出的\n * provider/model(sub-agent 递归继承)。常规 tool 不需要 tracing 包装,直接返回结果。\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 callCtx: AgentCallContext,\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(callCtx.agentName)) {\n declared.add(tool.name);\n }\n for (const sub of this.deps.resolveSubAgents(callCtx.agentName)) {\n declared.add(`agent.${sub.name}`);\n }\n if (!declared.has(name)) {\n return {\n error: `Tool \"${name}\" is not declared by agent \"${callCtx.agentName}\" (add it to the agent's tools/agents declaration)`,\n };\n }\n\n // sub-agent 递归(携带 callCtx,使其能继承 provider/model + 包装 TracingToolResult)\n if (name.startsWith('agent.')) {\n return await this.executeSubAgent(name.slice(6), args, callCtx);\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), { agent, provider, model, enableTracing })`\n * 走默认 reactLoop——继承父调用的 provider,sub 元数据声明 `model` 时优先用自身的,\n * 未声明时沿用父 model\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 callCtx: AgentCallContext,\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——providers/llms/访问器共享,无 per-agent 名绑定)\n const subAgent = new Agent(this.deps, 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:继承父调用的 provider;sub 元数据声明 model 时优先用自身的,\n // 未声明时沿用父 model。stringify args 作为 user 消息(agent-as-tool input 为开放式 JSON),\n // 传递 enableTracing 让 sub-agent 采集 trace\n const subMeta = this.deps.getAgent(subName);\n const result = await subAgent.run(typeof args === 'string' ? args : JSON.stringify(args), {\n agent: subName,\n provider: callCtx.provider,\n model: subMeta?.model ?? callCtx.model,\n enableTracing: callCtx.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 (callCtx.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 * 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.maxTurns` / `maxAgentDepth`\n * 3. 从 `@faapi/faapi` import 注册表/加载器访问器(getAgent / getTool / resolveAgentTools /\n * resolveSubAgents / loadAgentModule / loadToolModule)\n * 4. `registerAgentHandleFactory` 注册工厂——每次请求时构造 [Agent](./agent.md) 实例注入到\n * handler 的 `agent` 参数\n *\n * 无默认 agent / 默认 provider——`agent.run/stream` 每次调用显式传 `options.agent` +\n * `options.model` / `options.provider`。\n *\n * `agent.llms` 可选——未配置时工厂照常注册(外部 provider 模式),`agent.run/stream`\n * 需调用方传 `options.provider` 才能调用 LLM。只有插件未加载时 `agent` 参数才注入 `undefined`。\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\n // llms 可选——未配置时进入「外部 provider 模式」:工厂照常注册(providers 为空 Map),\n // agent.run/stream 需调用方传 options.provider 才能调用 LLM\n const llms = agentConfig?.llms ?? {};\n if (Object.keys(llms).length === 0) {\n console.log(\n '- @faapi/agent: no llms configured — use agent.run(input, { agent, provider }) to pass an external provider per call',\n );\n }\n\n // 创建 LLM provider 实例 Map(key 是 provider 名,来自 config.agent.llms)\n // 单例,所有请求共享;每个 provider 实例对应一个 LlmConfig\n // 无默认 provider——每次 agent.run/stream 调用经 options.model / options.provider 显式解析\n const providers = new Map<string, LLMProvider>();\n for (const [name, llmConfig] of Object.entries(llms)) {\n // 空 apiKey 照常注册(部分网关/本地模型场景无需 key),但启动日志显性提示——\n // 否则「key 未配置」延迟到首次 LLM 调用才暴露为上游 401,且错误文案来自上游,难排查\n if (!llmConfig.apiKey || llmConfig.apiKey.trim() === '') {\n console.warn(\n `! @faapi/agent: config.agent.llms.${name}.apiKey is empty, requests to this provider will omit Authorization header (upstream likely returns 401)`,\n );\n }\n providers.set(name, createProvider(llmConfig));\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 llms,\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 Object.keys(llms).length > 0\n ? `- @faapi/agent: providers [${Object.keys(llms).join(', ')}] — call agent.run(input, { agent, model }) to execute (no default agent/provider)`\n : '- @faapi/agent: no llms configured — use agent.run(input, { agent, provider }) to pass an external provider per call',\n );\n },\n};\n\nexport default agentPlugin;\n"],"mappings":"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","names":["maxRetries","err","tool","result","ctx"]}
1
+ {"version":3,"sources":["../src/providers/openai.ts","../src/provider.ts","../src/trace.ts","../src/reactLoop.ts","../src/agent.ts","../src/toolSchemaResolver.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 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 /** thinking 模型推理内容(DeepSeek / Qwen 线格式) */\n reasoning_content?: string | null;\n /** thinking 模型推理内容(OpenRouter 线格式) */\n reasoning?: string | null;\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 /** thinking 模型推理增量(DeepSeek / Qwen 线格式) */\n reasoning_content?: string | null;\n /** thinking 模型推理增量(OpenRouter 线格式) */\n reasoning?: string | null;\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 // 规范形即 OpenAI 形状:messages 恒等透传(剥离框架扩展字段 reasoning_content,\n // 推理内容是解析产物不回传——DeepSeek 多轮回传直接 400,OpenAI 等拒绝未知字段)\n messages: stripReasoningFromMessages(request.messages),\n };\n\n if (request.tools && request.tools.length > 0) {\n body.tools = request.tools;\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 = normalizeToolCalls(msg.tool_calls, bodyText, response.status);\n\n // assistant 消息按规范形构造(OpenAI 形状,tool_calls 原样保留字符串参数)\n const message: LLMMessage = { role: 'assistant', content };\n if (toolCalls) message.tool_calls = toolCalls;\n // thinking 模型推理内容(解析产物,不回传——发送侧由 stripReasoningFromMessages 剥离)\n const reasoning = extractReasoning(msg);\n if (reasoning) message.reasoning_content = reasoning;\n\n return {\n message,\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 // 推理内容增量(thinking 模型,先于 content 输出)\n const reasoning = extractReasoning(delta);\n if (reasoning) {\n yield { deltaReasoning: reasoning };\n }\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/**\n * 提取 thinking 模型的推理内容(规范形映射)\n *\n * 兼容两种线格式:`reasoning_content`(DeepSeek / Qwen 事实标准)优先,\n * 缺失时读 `reasoning`(OpenRouter 形状)。两者皆无/为空返回 `undefined`。\n */\nfunction extractReasoning(source: {\n reasoning_content?: string | null;\n reasoning?: string | null;\n}): string | undefined {\n if (typeof source.reasoning_content === 'string' && source.reasoning_content.length > 0) {\n return source.reasoning_content;\n }\n if (typeof source.reasoning === 'string' && source.reasoning.length > 0) {\n return source.reasoning;\n }\n return undefined;\n}\n\n/**\n * 剥离 messages 上的 `reasoning_content`(请求侧不回传,线格式合法性由 provider 边界兜底)\n *\n * `reasoning_content` 是响应解析产物:DeepSeek 明确要求多轮对话不回传(回传 400),\n * OpenAI 等拒绝未知字段。任一消息都不带该字段时**原数组原样返回**(恒等透传零拷贝);\n * 带该字段的消息浅拷贝副本剥离,其余消息保持原引用。业务方经续跑历史 / 自定义\n * messages 传入的带推理内容历史同样被剥离。\n */\nfunction stripReasoningFromMessages(messages: LLMMessage[]): LLMMessage[] {\n if (!messages.some((m) => m.reasoning_content !== undefined)) {\n return messages;\n }\n return messages.map((m) => {\n if (m.reasoning_content === undefined) return m;\n const { reasoning_content: _stripped, ...rest } = m;\n return rest;\n });\n}\n\n/**\n * OpenAI tool_calls 数组 → 规范形 LLMToolCall[]\n *\n * 补全缺失的 id/name 默认值并**校验 arguments 可解析**(不合法抛\n * `LLMProviderError`,fail-fast 在 provider 边界),`arguments` 保持线格式\n * JSON 字符串——解析边界收敛在 reactLoop(执行前 parse)。\n */\nfunction normalizeToolCalls(\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 try {\n 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 type: 'function',\n function: { name: tc?.function?.name ?? '', arguments: argsStr },\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(snake_case,缺省补 0) */\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 prompt_tokens: u.prompt_tokens ?? 0,\n completion_tokens: u.completion_tokens ?? 0,\n total_tokens: 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[](arguments 保持 JSON 字符串,校验可解析)+ 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 const argsStr = acc.argsString || '{}';\n try {\n JSON.parse(argsStr);\n } catch {\n const excerpt = argsStr.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 type: 'function',\n function: { name: acc.name, arguments: argsStr },\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 * 对话消息(OpenAI chat completions 规范形)\n *\n * 四种 role 与 OpenAI chat completions 一致:\n * - `system` —— 系统提示词(agent 的 systemPrompt)\n * - `user` —— 用户输入\n * - `assistant` —— LLM 回复(可能含 tool_calls)\n * - `tool` —— tool 执行结果(需带 tool_call_id 标识对应哪个 tool_call)\n *\n * 字段拼写与 OpenAI 线格式一致(`tool_calls` / `tool_call_id`),OpenAI provider\n * 对 messages 恒等透传,观测/存储/展示消费方按 OpenAI 形状处理即可。\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 tool_call_id?: string;\n /** role='assistant' 时:LLM 请求的 tool 调用(reactLoop 据此执行 tool) */\n tool_calls?: LLMToolCall[];\n /**\n * role='assistant' 时:thinking 模型的推理内容(DeepSeek 线格式字段名)\n *\n * provider 响应解析产物(非流式含完整内容),供业务方展示/审计;\n * **不回传 LLM API**——OpenAI provider 发送请求时剥离(DeepSeek 多轮回传\n * 推理内容直接 400,OpenAI 等拒绝未知字段),reactLoop 组装历史时同样剥离。\n * 兼容解析:线格式 `reasoning_content` 优先,缺失时读 `reasoning`(OpenRouter 形状)。\n */\n reasoning_content?: string;\n}\n\n/**\n * assistant 消息内的 tool 调用(OpenAI chat completions 规范形)\n *\n * 由 LLM 在 assistant 消息中返回。`function.arguments` 是线格式的 JSON **字符串**\n * (不预解析)——消息历史可原样透传与持久化;[reactLoop](./reactLoop.md) 在执行前\n * JSON.parse,tool 执行函数 / 鉴权钩子 / trace 事件拿到的都是已 parse 的对象。\n */\nexport interface LLMToolCall {\n /** tool call ID(provider 分配,用于匹配 tool 结果) */\n id: string;\n /** 固定 `'function'`(OpenAI 线格式) */\n type: 'function';\n function: {\n /** tool 名(匹配 LLMToolDefinition.function.name) */\n name: string;\n /** tool 参数(JSON 字符串,线格式原样) */\n arguments: string;\n };\n}\n\n/**\n * Tool 定义(OpenAI chat completions 规范形)\n *\n * 由 [reactLoop](./reactLoop.md) 从 [toolRegistry](../../faapi/src/injection/toolRegistry.md)\n * + [agentRegistry.resolveSubAgents](../../faapi/src/injection/agentRegistry.md) 组装:\n * - 常规 tool:`function.parameters` 来自 AST 提取的 zod schema(JSON Schema 形式)\n * - agent-as-tool:`function.parameters` 为自由 schema(agent 参数开放)\n */\nexport interface LLMToolDefinition {\n /** 固定 `'function'`(OpenAI 线格式) */\n type: 'function';\n function: {\n /** tool 名(如 `weather.getWeather` 或 `agent.researcher`) */\n name: string;\n /** tool 描述(对 LLM 可见,引导 LLM 选择调用) */\n description?: string;\n /** JSON Schema 对象(描述 tool 参数结构) */\n parameters?: Record<string, unknown>;\n };\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 *\n * `messages` 为中断时刻的部分对话历史(截至最后一个完整轮组),由\n * [reactLoop](./reactLoop.md) 在循环边界附加(provider 层不感知历史,恒为空数组)。\n * 业务方持久化后经 `config.messages` / `AgentRunOptions.messages` 从断点续跑,\n * 语义详见 [reactLoop.md](./reactLoop.md) 中断恢复章节。\n */\nexport class AgentAbortError extends Error {\n /** 中断时刻的部分对话历史(完整轮组快照,可直接用于续跑) */\n readonly messages: LLMMessage[];\n\n constructor(message = 'Agent execution aborted', messages: LLMMessage[] = []) {\n super(message);\n this.name = 'AgentAbortError';\n this.messages = messages;\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.tool_calls` 不为空时 stopReason 应为 `'tool_calls'`。\n */\nexport interface LLMResponse {\n /** assistant 消息(OpenAI 规范形,含 content + 可选 tool_calls) */\n message: LLMMessage;\n /** 停止原因(reactLoop 据此判断是否进入下一轮) */\n stopReason: LLMStopReason;\n /** token 用量(部分 provider 不返回) */\n usage?: LLMUsage;\n}\n\n/**\n * Token 用量(OpenAI chat completions 规范形)\n */\nexport interface LLMUsage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n}\n\n/**\n * stream 的单个 chunk\n *\n * provider 内部流抽象(非 OpenAI 分片 delta 线格式):\n * - 内容流:`deltaContent` 为增量 token\n * - 推理内容流(thinking 模型):`deltaReasoning` 为推理增量(与 `deltaContent` 同为\n * 增量语义,消费方按到达顺序各自拼接)\n * - tool 调用:累积完成后在最终 chunk 一并 emit `toolCalls`(OpenAI 规范形,\n * `function.arguments` 为 JSON 字符串)\n * - 结束:最终 chunk 含 `finishReason` + 可选 `usage`\n */\nexport interface LLMStreamChunk {\n /** 增量内容(streaming token) */\n deltaContent?: string;\n /** 推理内容增量(thinking 模型,如 DeepSeek `reasoning_content` / OpenRouter `reasoning`) */\n deltaReasoning?: 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 LLMResponse,\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 * 初始对话历史(续跑 / 多轮对话)\n *\n * 提供时以其为基础(历史应含 system):历史无 `system` 消息且配置了\n * `systemPrompt` 时自动在最前插入(agent 人格不因续跑丢失);`input` 非空时\n * 追加为新的 `user` 消息(多轮对话),为空时纯续跑。历史经 `Agent` 层结构校验\n * (assistant.tool_calls 与 tool 结果按 tool_call_id 配对完整)。\n * 续跑源见 [reactLoop.md](./reactLoop.md) 中断恢复章节。\n */\n messages?: LLMMessage[];\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 /**\n * 最终 assistant 的推理内容(thinking 模型,多轮时中间轮的推理不保留;\n * 无推理内容时不存在)。历史 messages 中的 assistant 消息已剥离推理内容,\n * 仅此字段与 trace 的 `llm_call.response.reasoning_content` 可读。\n */\n reasoning?: 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 * - `deltaReasoning` — LLM 推理内容增量(thinking 模型,含中间 tool 轮)\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 /** LLM 推理内容增量(thinking 模型,见 reactLoop.md thinking 章节) */\n deltaReasoning?: 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 / deltaReasoning / toolCall / toolResult / done 互斥,一个 chunk 至多一个字段。\n */\n traceEvent?: AgentTraceEvent;\n /** 循环结束 */\n done?: {\n content: string;\n /** 最终轮的完整推理内容(thinking 模型;无推理内容时不存在) */\n reasoning?: 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 /** 超限时的完整对话历史(业务方可提高 maxTurns 后经 `config.messages` 续跑,轮数重新计数) */\n readonly messages: LLMMessage[];\n\n constructor(message: string, maxTurns: number, messages: LLMMessage[] = []) {\n super(message);\n this.name = 'ReactLoopError';\n this.maxTurns = maxTurns;\n this.messages = messages;\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/**\n * 剥离 assistant 消息上的 `reasoning_content`(历史保持 OpenAI 线格式纯净)\n *\n * 推理内容只透出给调用方(`ReactLoopResult.reasoning` / `deltaReasoning` / trace 的\n * `llm_call.response`),不进入对话历史——历史是发给 LLM 的(DeepSeek 多轮回传推理\n * 内容直接 400)也是持久化 / 续跑 / `AgentAbortError.messages` / `ReactLoopError.messages`\n * 的来源。无该字段时原引用返回(零拷贝常态路径)。\n */\nfunction stripReasoning(message: LLMMessage): LLMMessage {\n if (message.reasoning_content === undefined) return message;\n const { reasoning_content: _stripped, ...rest } = message;\n return rest;\n}\n\n/** 累加 usage */\nfunction accumulateUsage(a: LLMUsage | undefined, b: LLMUsage): LLMUsage {\n if (!a) return { ...b };\n return {\n prompt_tokens: a.prompt_tokens + b.prompt_tokens,\n completion_tokens: a.completion_tokens + b.completion_tokens,\n total_tokens: a.total_tokens + b.total_tokens,\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/**\n * 解析 tool_call 的 `function.arguments`(OpenAI 线格式 JSON 字符串)为对象\n *\n * 解析边界收敛在此——tool 执行函数 / 鉴权钩子 / trace 事件拿到的都是已 parse 的对象。\n * 解析失败抛错,由调用方的 tool 错误路径 catch 后回传 LLM(LLM 可修正参数重试)。\n */\nfunction parseToolCallArguments(toolCall: LLMToolCall): Record<string, unknown> {\n const raw = toolCall.function.arguments;\n if (!raw) return {};\n return JSON.parse(raw) as Record<string, unknown>;\n}\n\n/**\n * 构造循环初始 messages(含续跑语义,见 reactLoop.md 中断恢复章节)\n *\n * - `config.messages` 提供(续跑 / 多轮对话):以其为基础——历史无 `system` 时\n * 自动前置 `systemPrompt`(agent 人格不因续跑丢失);非空 `input` 追加为新的\n * user 消息,空 input 纯续跑\n * - 未提供:`systemPrompt`(可选)+ user input(现状行为,input 为空时 user 内容为空串,\n * 输入守卫由 Agent 层负责)\n */\nfunction initLoopMessages(input: string | undefined, config: ReactLoopConfig): LLMMessage[] {\n if (config.messages?.length) {\n // 剥离业务方历史上的 reasoning_content(推理内容不进对话历史,见 stripReasoning)\n const messages = config.messages.map(stripReasoning);\n if (config.systemPrompt && !messages.some((m) => m.role === 'system')) {\n messages.unshift({ role: 'system', content: config.systemPrompt });\n }\n if (input) {\n messages.push({ role: 'user', content: input });\n }\n return messages;\n }\n return buildInitialMessages(input ?? '', config.systemPrompt);\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.tool_calls) {\n chars += JSON.stringify(message.tool_calls).length;\n }\n if (message.tool_call_id) {\n chars += message.tool_call_id.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 用户输入(续跑场景可为空,历史经 `config.messages` 提供)\n * @param config 循环配置\n * @returns 最终结果(content + reasoning + messages + turns + stopReason + usage)\n * @throws {ReactLoopError} 超出 maxTurns(`error.messages` 携带完整历史,可续跑)\n * @throws {AgentAbortError} 中断(`error.messages` 携带断点历史,可续跑)\n * @throws {Error} provider.complete 抛错时立即传播\n */\nexport async function reactLoop(\n input: string | undefined,\n config: ReactLoopConfig,\n): Promise<ReactLoopResult> {\n const enableTracing = config.enableTracing ?? false;\n const messages = initLoopMessages(input, config);\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n const extras = buildRequestExtras(config);\n let totalUsage: LLMUsage | undefined;\n /** 最终轮 assistant 的推理内容(thinking 模型;每轮覆盖,循环结束时即最终轮值) */\n let finalReasoning: string | 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(undefined, messages);\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 // 执行中取消由 provider 请求中断传播——附上断点历史重抛(不含未完成的 assistant 消息,\n // 轮组原子性见 reactLoop.md 中断恢复章节)\n let response: LLMResponse;\n try {\n response = await config.provider.complete({\n messages: [...outgoing],\n ...extras,\n signal: config.signal,\n });\n } catch (err) {\n if (err instanceof AgentAbortError) {\n throw new AgentAbortError(err.message, messages);\n }\n throw err;\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 // 原始 assistant 消息(含 reasoning_content)——trace 保留完整 LLM 返回,\n // 与历史剥离策略互补(见 trace.md「llm_call.response 与 thinking」)\n response: response.message,\n stopReason: response.stopReason,\n usage: response.usage,\n });\n }\n\n // 把 assistant 消息加入历史(剥离 reasoning_content——推理内容不进对话历史,\n // 见 stripReasoning 与 reactLoop.md thinking 章节)\n finalReasoning = response.message.reasoning_content;\n messages.push(stripReasoning(response.message));\n\n // 非 tool_calls → 循环结束\n if (response.stopReason !== 'tool_calls' || !response.message.tool_calls) {\n const traceEndedAt = enableTracing ? nowMs() : 0;\n const result: ReactLoopResult = {\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 if (finalReasoning !== undefined) {\n result.reasoning = finalReasoning;\n }\n return result;\n }\n\n // 并行执行同轮全部 tool_call——多个独立 tool 的总耗时从「各 tool 之和」\n // 降为「最慢一个」。每个 toolCall 独立 try/catch(单个失败不影响其余),\n // 结果按下方的 tool_calls 声明顺序回传(与完成顺序无关,保证 tool 配对语义)。\n // 注:beforeToolCall/afterToolCall 钩子会并发触发,业务方钩子不应依赖调用顺序。\n // 流式路径(reactLoopStream)保持串行——yield 顺序受消费端约束。\n // arguments 解析边界在此收拢:线格式 JSON 字符串在此 parse,执行函数 /\n // 鉴权钩子 / trace 事件拿到的都是已 parse 的对象;解析失败走 tool 错误路径回传。\n const settled = await Promise.all(\n response.message.tool_calls.map(async (toolCall) => {\n const toolStartedAt = enableTracing ? nowMs() : 0;\n const toolName = toolCall.function.name;\n let args: Record<string, unknown> = {};\n let resultStr: string;\n let rawResult: unknown | TracingToolResult;\n let toolErr: unknown;\n let hasError = false;\n try {\n args = parseToolCallArguments(toolCall);\n rawResult = await config.executeTool(toolName, args);\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 toolName,\n args,\n toolStartedAt,\n toolEndedAt,\n resultStr,\n rawResult,\n toolErr,\n hasError,\n };\n }),\n );\n\n for (const {\n toolCall,\n toolName,\n args,\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(toolName),\n input: JSON.stringify(args),\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: toolName,\n arguments: args,\n result: resultStr,\n error: hasError ? stringifyError(toolErr) : undefined,\n });\n }\n }\n\n messages.push({\n role: 'tool',\n content: resultStr,\n tool_call_id: 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 messages,\n );\n}\n\n// ─── reactLoopStream(流式)──────────────────────────\n\n/**\n * 执行 ReAct 循环(流式)\n *\n * 使用 `provider.stream()` 异步迭代 chunks,yield `deltaContent` + `deltaReasoning` + `toolCall` + `toolResult` + `done`。\n *\n * @param input 用户输入(续跑场景可为空,历史经 `config.messages` 提供)\n * @param config 循环配置\n * @yields {ReactLoopStreamChunk} 流式 chunk\n * @throws {ReactLoopError} 超出 maxTurns(`error.messages` 携带完整历史,可续跑)\n * @throws {AgentAbortError} 中断(`error.messages` 携带断点历史,可续跑)\n * @throws {Error} provider.stream 抛错时立即传播\n */\nexport async function* reactLoopStream(\n input: string | undefined,\n config: ReactLoopConfig,\n): AsyncIterable<ReactLoopStreamChunk> {\n const enableTracing = config.enableTracing ?? false;\n const messages = initLoopMessages(input, config);\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(undefined, messages);\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 turnReasoning = '';\n let toolCalls: LLMToolCall[] | undefined;\n let finishReason: LLMStopReason | undefined;\n let turnUsage: LLMUsage | undefined;\n\n // 执行中取消由 provider 流中断传播——附上断点历史重抛。当前轮已 yield 的部分\n // deltaContent 属于未完成轮组,不入历史(轮组原子性见 reactLoop.md 中断恢复章节)\n try {\n for await (const chunk of config.provider.stream({\n messages: [...outgoing],\n ...extras,\n signal: config.signal,\n })) {\n // 推理内容增量(thinking 模型,中间 tool 轮同样透出——业务方可完整展示思考过程)\n if (typeof chunk.deltaReasoning === 'string' && chunk.deltaReasoning.length > 0) {\n turnReasoning += chunk.deltaReasoning;\n yield { deltaReasoning: chunk.deltaReasoning };\n }\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 } catch (err) {\n if (err instanceof AgentAbortError) {\n throw new AgentAbortError(err.message, messages);\n }\n throw err;\n }\n\n // 把 assistant 消息加入历史(含 tool_calls,供下一轮 LLM 上下文;规范形,\n // function.arguments 保持线格式 JSON 字符串;本就是新构造对象,天然无推理内容)\n const assistantMessage: LLMMessage = {\n role: 'assistant',\n content: turnContent,\n };\n if (toolCalls) {\n assistantMessage.tool_calls = toolCalls;\n }\n messages.push(assistantMessage);\n\n if (enableTracing) {\n const llmEndedAt = nowMs();\n // trace 保留该轮完整 LLM 返回(含累积的推理内容),与历史剥离互补;\n // 无推理内容时不拷贝(零开销常态路径)\n const tracedResponse = turnReasoning\n ? { ...assistantMessage, reasoning_content: turnReasoning }\n : assistantMessage;\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: tracedResponse,\n stopReason: finishReason ?? 'other',\n usage: turnUsage,\n },\n };\n }\n\n // 非 tool_calls → 循环结束\n if (finishReason !== 'tool_calls' || !toolCalls) {\n const donePayload: NonNullable<ReactLoopStreamChunk['done']> = {\n content: turnContent,\n turns,\n stopReason: finishReason ?? 'other',\n usage: totalUsage,\n };\n if (turnReasoning) {\n donePayload.reasoning = turnReasoning;\n }\n yield { done: donePayload };\n return;\n }\n\n // 执行每个 tool call(arguments 解析边界与非流式路径一致:先 parse,\n // 解析失败走 tool 错误路径回传 LLM)\n for (const toolCall of toolCalls) {\n const toolName = toolCall.function.name;\n const toolStartedAt = enableTracing ? nowMs() : 0;\n let args: Record<string, unknown> = {};\n let resultStr: string;\n let rawResult: unknown | TracingToolResult;\n let toolErr: unknown;\n let hasError = false;\n try {\n args = parseToolCallArguments(toolCall);\n yield { toolCall: { name: toolName, arguments: args } };\n rawResult = await config.executeTool(toolName, args);\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: toolName, 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(toolName),\n input: JSON.stringify(args),\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: toolName,\n arguments: args,\n result: resultStr,\n error: hasError ? stringifyError(toolErr) : undefined,\n },\n };\n }\n }\n\n messages.push({\n role: 'tool',\n content: resultStr,\n tool_call_id: 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 messages,\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 { createProvider } from './provider';\nimport type { LLMMessage, 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/** 合法消息 role(与 LLMMessage 的 role 联合一致,运行时校验反序列化历史用) */\nconst VALID_MESSAGE_ROLES = new Set(['system', 'user', 'assistant', 'tool']);\n\n/**\n * 判断是否为 LLMProvider 实例(有 `complete` / `stream` 方法)\n *\n * 用于 `options.provider` 的运行时形状判别——`LLMProvider` 实例直接使用,\n * `LlmConfig` 配置对象走 `createProvider` 现场创建。\n */\nfunction isProviderInstance(value: unknown): value is LLMProvider {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as LLMProvider).complete === 'function' &&\n typeof (value as LLMProvider).stream === 'function'\n );\n}\n\n/**\n * 判断是否为 LlmConfig 配置对象(含字符串 `provider` 字段)\n */\nfunction isProviderConfig(value: unknown): value is LlmConfig {\n return (\n typeof value === 'object' && value !== null && typeof (value as LlmConfig).provider === 'string'\n );\n}\n\n/**\n * 物化外部 provider(`options.provider` → LLMProvider 实例)\n *\n * - `LLMProvider` 实例 → 直接使用(自定义内部模型网关等)\n * - `LlmConfig` 配置对象 → `createProvider` 现场创建(浅拷贝 + `models` 兜底 `{}`,\n * 不改调用方对象;每次调用独立创建,无共享状态,不进 providers Map)\n * - 两者皆非 → 抛 `AgentError`(不降级猜测)\n *\n * @throws {AgentError} 形式非法(既非 LlmConfig 也非 LLMProvider)\n */\nfunction materializeProvider(external: LlmConfig | LLMProvider): LLMProvider {\n if (isProviderInstance(external)) return external;\n if (isProviderConfig(external)) {\n return createProvider({ ...external, models: external.models ?? {} });\n }\n throw new AgentError(\n 'options.provider must be an LlmConfig object (with a string \"provider\" field) or an LLMProvider instance (with complete/stream methods)',\n );\n}\n\n/**\n * 校验续跑历史结构(见 reactLoop.md 中断恢复章节)\n *\n * `AgentRunOptions.messages` 是业务方持久化后回传的历史,最常见的损坏是截断在\n * 轮组中间(assistant.toolCalls 缺 tool 结果)或反序列化出非法 role——直接发给\n * LLM API 只会得到模糊的 400。此处早失败(抛 `AgentError`,不发起 LLM 请求)。\n *\n * 校验规则:\n * - role 必须是 system / user / assistant / tool 之一\n * - assistant 消息带 `toolCalls` 时,其后必须紧跟对应数量的 tool 结果消息\n * (按 `toolCallId` 配对,在任何非 tool 消息之前)\n *\n * @throws {AgentError} 历史结构非法(消息含索引与 toolCallId,可定位损坏点)\n */\nfunction validateResumeHistory(messages: LLMMessage[]): void {\n messages.forEach((message, index) => {\n if (!VALID_MESSAGE_ROLES.has(message.role)) {\n throw new AgentError(\n `Invalid resume history at messages[${index}]: unknown role \"${String(message.role)}\"`,\n );\n }\n if (message.role === 'assistant' && message.tool_calls?.length) {\n const missing = new Set(message.tool_calls.map((call) => call.id));\n for (let j = index + 1; j < messages.length && messages[j]!.role === 'tool'; j++) {\n missing.delete(messages[j]!.tool_call_id ?? '');\n }\n if (missing.size > 0) {\n throw new AgentError(\n `Invalid resume history at messages[${index}]: assistant tool call(s) [${Array.from(missing).join(', ')}] have no matching tool result (history truncated mid-turn — persist the full turn group from AgentAbortError.messages / ReactLoopError.messages)`,\n );\n }\n }\n });\n}\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 * 本次调用的解析结果([buildLoopConfig](#buildLoopConfig) 解析后经闭包传给 executeTool)\n *\n * - `agentName`——本次调用的有效 agent 名(执行白名单按它的声明集合校验)\n * - `enableTracing`——sub-agent 调用是否包装 TracingToolResult\n * - `provider` / `model`——解析出的 LLM 入口,sub-agent 递归继承(sub 元数据\n * 声明 `model` 时优先用自身的)\n */\ninterface AgentCallContext {\n agentName: string;\n enableTracing: boolean;\n provider: LLMProvider;\n model: string | undefined;\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;未配置时为空 Map) */\n providers: Map<string, LLMProvider>;\n /** LLM provider 配置映射(含 models,用于 options.model key 解析;llms 未配置时为空对象) */\n llms: Record<string, LlmConfig>;\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 + 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 返回后填充 `options.agent`,让顶层 trace 标识\"是哪个 agent 跑的\"。\n *\n * @param input 用户输入(可选——续跑场景不传新输入;input 与 `options.messages`\n * 都为空时抛 `AgentError`)\n * @param options 本次调用配置——`agent`(agent 名,必须显式传,无默认 agent)/\n * provider(外部 provider)/ model(字符串 key)/ temperature /\n * maxTokens / messages / enableTracing\n * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))\n * @returns 最终结果(content + messages + turns + stopReason + usage + trace?)\n * @throws {AgentError} 未传 options.agent;agent 未注册;input 与 messages 都为空;\n * 续跑历史结构非法;provider/model 无法解析\n * @throws {ReactLoopError} 超出 maxTurns(`error.messages` 携带完整历史,可续跑)\n * @throws {AgentAbortError} 中断(`error.messages` 携带断点历史,可续跑)\n * @throws {Error} provider.complete 抛错时立即传播\n */\n async run(input?: string, options?: AgentRunOptions): Promise<ReactLoopResult> {\n const config = await this.buildLoopConfig(input, 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 ?? '';\n }\n return result;\n }\n\n /**\n * 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)\n *\n * @param input 用户输入(可选——续跑场景不传新输入;input 与 `options.messages`\n * 都为空时抛 `AgentError`)\n * @param options 本次调用配置——`agent`(必须显式传)/ provider / model /\n * temperature / maxTokens / messages\n * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))\n * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)\n * @throws {AgentError} 未传 options.agent;agent 未注册;input 与 messages 都为空;\n * 续跑历史结构非法;provider/model 无法解析\n * @throws {ReactLoopError} 超出 maxTurns(`error.messages` 携带完整历史,可续跑)\n * @throws {AgentAbortError} 中断(`error.messages` 携带断点历史,可续跑)\n * @throws {Error} provider.stream 抛错时立即传播\n */\n async *stream(input?: string, options?: AgentRunOptions): AsyncIterable<ReactLoopStreamChunk> {\n const config = await this.buildLoopConfig(input, options);\n yield* reactLoopStream(input, config);\n }\n\n /**\n * 把指定 agent 包装为 `AgentToolDescriptor` 供 LLM 当 tool 调用\n *\n * 与 [agentRegistry.asTool](../../faapi/src/injection/agentRegistry.md) 同构——\n * Agent 类自带此方法便于在注入器场景直接调用(不必再过注册表)。\n *\n * @param name agent 名(显式指定——无默认 agent)\n * @returns `AgentToolDescriptor` 或 `undefined`(agent 未注册)\n */\n asTool(name: string): AgentToolDescriptor | undefined {\n const meta = this.deps.getAgent(name);\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`(必须显式传——无默认 agent,不传抛 AgentError)\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\n *\n * `options.provider`(外部 provider)存在时由 {@link resolveExternalProvider} 物化,\n * 优先级最高——`options.model` 变为原始 model 名原样透传(不解析 llms key)。\n * 否则 `options.model` 是字符串 key,由 {@link resolveModelKey} 解析为 provider + model\n * (支持 llms key 精确匹配 / `provider/model` 一体化 / 纯 model 名模糊匹配;\n * 未传 `options.model` 时用 agent 元数据 `config.model` 作为缺省 key)。\n * 详见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。\n *\n * **输入守卫**(续跑入口,见 [reactLoop.md](./reactLoop.md) 中断恢复章节):\n * `input` 与 `options.messages` 都为空时抛 `AgentError`(不发送空请求);\n * `options.messages` 提供时先经 `validateResumeHistory` 结构校验,非法抛\n * `AgentError`,不发起 LLM 请求。\n */\n private async buildLoopConfig(\n input: string | undefined,\n options?: AgentRunOptions,\n ): Promise<ReactLoopConfig> {\n // 输入守卫:全新对话必须有 input,续跑必须有 messages(空 input + messages = 纯续跑)\n if (!input && !options?.messages?.length) {\n throw new AgentError(\n 'agent.run/stream requires non-empty input, or options.messages to resume from (AgentAbortError.messages / ReactLoopError.messages / previous result.messages)',\n );\n }\n if (options?.messages?.length) {\n validateResumeHistory(options.messages);\n }\n\n // 无默认 agent——options.agent 必须显式传\n const agentName = options?.agent;\n if (!agentName) {\n throw new AgentError(\n 'agent.run/stream requires options.agent (no default agent) — pass { agent: \"name\" } to specify which agent to run',\n );\n }\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 // 解析 provider + model:options.provider(外部 provider)优先级最高,存在时跳过\n // options.model 的 llms key 解析(model 原样透传);否则按字符串 key 规则解析\n const { provider, model } =\n options?.provider !== undefined\n ? this.resolveExternalProvider(options.provider, options?.model)\n : 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 // 本次调用的解析结果——executeTool / executeSubAgent 复用(白名单校验用 agentName,\n // sub-agent 递归继承 provider/model)\n const callCtx = { agentName, enableTracing, provider, model };\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 messages: options?.messages,\n enableTracing,\n executeTool: async (name, args) => this.executeTool(name, args, callCtx),\n };\n }\n\n /**\n * 解析外部 provider(`options.provider`)→ provider + model\n *\n * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.provider` 外部 provider」章节:\n * - `LLMProvider` 实例 → 直接使用,`modelKey` 原样透传(可为 `undefined`,自定义 provider 自决)\n * - `LlmConfig` 配置对象 → `createProvider` 现场创建,`modelKey` 原样透传;\n * 缺省回落该 config `models` 第一个 key,两者皆无抛 `AgentError`(早失败,不发请求)\n * - `modelKey` 不做 llms key 解析、不拆 `/`(支持 OpenRouter 等带斜杠的 model id)\n *\n * 仅本次调用生效:不进 providers Map、sub-agent 递归不继承(executeSubAgent 构造\n * subDeps 时不携带 options,sub-agent 走默认解析链路)。\n *\n * @throws {AgentError} provider 形式非法;LlmConfig 形式下 model 缺失\n */\n private resolveExternalProvider(\n external: LlmConfig | LLMProvider,\n modelKey: string | undefined,\n ): { provider: LLMProvider; model: string | undefined } {\n const provider = materializeProvider(external);\n if (isProviderInstance(external)) {\n return { provider, model: modelKey };\n }\n const firstModel = Object.keys(external.models ?? {})[0];\n const model = modelKey ?? firstModel;\n if (model === undefined) {\n throw new AgentError(\n 'External provider requires a model: pass options.model or declare models in the provider config',\n );\n }\n return { provider, model };\n }\n\n /**\n * 解析 `options.model` 字符串 key → provider + model\n *\n * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。\n * 无默认 provider——`key` 未传时用 agent 元数据 `config.model` 作为缺省 key;\n * 两者皆无抛 `AgentError`(要求调用方传 `options.model` 或 `options.provider`)。\n * 1. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key\n * (该 provider 未声明 `models` 时回落 `meta.model`)\n * 2. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model\n * (要求该 model 在 `deps.llms[provider].models` 里)\n * 3. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找\n * - 唯一 → 该 provider + 该 model\n * - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)\n * - 无 → 抛 `AgentError`\n *\n * @throws {AgentError} key 与 `meta.model` 均缺省;key 解析失败(provider/model\n * 不存在或歧义)\n */\n private resolveModelKey(\n key: string | undefined,\n meta: AgentCore,\n ): { provider: LLMProvider; model: string | undefined } {\n // 无默认 provider——未传 options.model 时用 agent 元数据 model 作为缺省 key\n const effectiveKey = key ?? meta.model;\n if (effectiveKey === undefined) {\n throw new AgentError(\n 'No LLM provider resolved: pass options.model (a provider key / \"provider/model\" / model name from config.agent.llms), options.provider (external provider), or declare model in the agent config',\n );\n }\n\n // 规则 1:精确匹配 providers key(如 'openai')\n const byProviderKey = this.deps.providers.get(effectiveKey);\n if (byProviderKey) {\n const llmConfig = this.deps.llms[effectiveKey];\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 (effectiveKey.includes('/')) {\n const slashIdx = effectiveKey.indexOf('/');\n const providerName = effectiveKey.slice(0, slashIdx);\n const modelName = effectiveKey.slice(slashIdx + 1);\n const provider = this.deps.providers.get(providerName);\n if (!provider) {\n throw new AgentError(\n `Unknown provider \"${providerName}\" in model key \"${effectiveKey}\". Declare it in config.agent.llms, or pass options.provider to use an external provider.`,\n );\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[effectiveKey]) {\n matches.push({ provider, providerName });\n }\n }\n if (matches.length === 1) {\n return { provider: matches[0]!.provider, model: effectiveKey };\n }\n if (matches.length > 1) {\n throw new AgentError(\n `Model \"${effectiveKey}\" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(', ')}). Use \"provider/model\" to disambiguate.`,\n );\n }\n throw new AgentError(\n `Model \"${effectiveKey}\" not found in any provider. Declare it in config.agent.llms.*.models, or pass options.provider to use an external provider.`,\n );\n }\n\n /**\n * 组装 LLM 可见 tool 列表(OpenAI chat completions 规范形)\n *\n * 合并两个来源(按 `function.name` 去重,先入者保留):\n * 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用\n * 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`\n *\n * 每个常规 tool 的 `function.parameters`:\n * - `resolveToolSchema` 提供 → 用其 `jsonSchema`\n * - 未提供 / tool 无 `inputTypeName` → 自由 schema `{ type: 'object' }`\n *\n * sub-agent 的 `function.parameters` 始终为 `{ 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 type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: schemaRes?.jsonSchema ?? { type: 'object' },\n },\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 type: 'function',\n function: {\n name,\n description: subAgent.description,\n parameters: { type: 'object' },\n },\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 * `callCtx` 由 [buildLoopConfig](#buildLoopConfig) 闭包捕获传入——本次调用的有效\n * agent 名(白名单校验)、enableTracing(sub-agent tracing 包装)与解析出的\n * provider/model(sub-agent 递归继承)。常规 tool 不需要 tracing 包装,直接返回结果。\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 callCtx: AgentCallContext,\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(callCtx.agentName)) {\n declared.add(tool.name);\n }\n for (const sub of this.deps.resolveSubAgents(callCtx.agentName)) {\n declared.add(`agent.${sub.name}`);\n }\n if (!declared.has(name)) {\n return {\n error: `Tool \"${name}\" is not declared by agent \"${callCtx.agentName}\" (add it to the agent's tools/agents declaration)`,\n };\n }\n\n // sub-agent 递归(携带 callCtx,使其能继承 provider/model + 包装 TracingToolResult)\n if (name.startsWith('agent.')) {\n return await this.executeSubAgent(name.slice(6), args, callCtx);\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), { agent, provider, model, enableTracing })`\n * 走默认 reactLoop——继承父调用的 provider,sub 元数据声明 `model` 时优先用自身的,\n * 未声明时沿用父 model\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 callCtx: AgentCallContext,\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——providers/llms/访问器共享,无 per-agent 名绑定)\n const subAgent = new Agent(this.deps, 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:继承父调用的 provider;sub 元数据声明 model 时优先用自身的,\n // 未声明时沿用父 model。stringify args 作为 user 消息(agent-as-tool input 为开放式 JSON),\n // 传递 enableTracing 让 sub-agent 采集 trace\n const subMeta = this.deps.getAgent(subName);\n const result = await subAgent.run(typeof args === 'string' ? args : JSON.stringify(args), {\n agent: subName,\n provider: callCtx.provider,\n model: subMeta?.model ?? callCtx.model,\n enableTracing: callCtx.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 (callCtx.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 * tool schema 解析工厂——把 tool 的 zod.js 解析为 AgentDeps.resolveToolSchema 契约值\n *\n * `AgentDeps.resolveToolSchema` 要求返回 [ToolSchemaResolution](./agent.md)\n * (`{ jsonSchema, validate }`),而 faapi 核心公开导出的\n * [loadToolSchema](../../faapi/src/loader/loadToolSchema.md) 返回的是\n * `{ schema, schemaName }` 原始 zod 模块——两者之间的装配(`z.toJSONSchema`\n * + `safeParse`)集中在本模块,经 `createToolSchemaResolver` 公开导出,\n * 插件 setup 与任务内组装 agent 共用同一实现,业务方无需镜像框架内部逻辑。\n *\n * 详见 [toolSchemaResolver.md](./toolSchemaResolver.md)。\n */\n\nimport { statSync } from 'node:fs';\nimport { z } from 'zod';\nimport { loadToolSchema, getToolSchemaPath, type ToolMetadata } from '@faapi/faapi';\nimport type { ToolSchemaResolution } from './agent';\n\n/**\n * 单次解析(无缓存)——loadToolSchema 加载 zod.js → 生成 JSON Schema + 校验函数\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 * 创建带 mtime 缓存的 tool schema 解析器\n *\n * 返回的函数满足 `AgentDeps.resolveToolSchema` 签名,供两处共用:\n * - `@faapi/agent` 插件 setup(传 `ctx.rootDir`,root + sub-agent 共享同一闭包缓存)\n * - 任务内组装 [Agent](./agent.md)(`TaskContext` 无 rootDir,缺省 `process.cwd()`——\n * faapi 服务进程 cwd 即项目根;建议任务文件模块级创建一次)\n *\n * **缓存语义**(闭包级 `Map<key, { mtimeMs, resolution }>`):\n * - 缓存键 `zodPath#inputTypeName`,每次查找 `statSync` 一次做 mtime 自校验——\n * mtime 变化即重新解析(dev reloadTools 重生成 zod.js 后自愈,prod 产物固化永远命中)\n * - in-flight Promise 直接入缓存:同一 tool 的并发调用共享同一次解析\n * - 每次 `createToolSchemaResolver` 调用返回独立缓存的 resolver\n *\n * zod.js 缺失 / tool 无 `inputTypeName` 时解析结果为 `undefined`——agent 用\n * 自由 schema `{ type: 'object' }`,LLM 自由传参。\n *\n * @param options.rootDir 项目根目录(缺省 `process.cwd()`)\n */\nexport function createToolSchemaResolver(options?: { rootDir?: string }) {\n const rootDir = options?.rootDir ?? process.cwd();\n const schemaCache = new Map<\n string,\n { mtimeMs: number; resolution: Promise<ToolSchemaResolution | undefined> }\n >();\n return (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","/**\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 * 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.maxTurns` / `maxAgentDepth`\n * 3. 从 `@faapi/faapi` import 注册表/加载器访问器(getAgent / getTool / resolveAgentTools /\n * resolveSubAgents / loadAgentModule / loadToolModule),tool schema 解析用\n * [createToolSchemaResolver](./toolSchemaResolver.md)(setup 闭包级缓存)\n * 4. `registerAgentHandleFactory` 注册工厂——每次请求时构造 [Agent](./agent.md) 实例注入到\n * handler 的 `agent` 参数\n *\n * 无默认 agent / 默认 provider——`agent.run/stream` 每次调用显式传 `options.agent` +\n * `options.model` / `options.provider`。\n *\n * `agent.llms` 可选——未配置时工厂照常注册(外部 provider 模式),`agent.run/stream`\n * 需调用方传 `options.provider` 才能调用 LLM。只有插件未加载时 `agent` 参数才注入 `undefined`。\n *\n * 详见 [plugin.md](./plugin.md)。\n */\n\nimport {\n loadAgentModule,\n loadToolModule,\n type FaapiPlugin,\n type PluginContext,\n type AgentConfig,\n} from '@faapi/faapi';\nimport { Agent, type AgentRuntimeConfig } from './agent';\nimport { createToolSchemaResolver } from './toolSchemaResolver';\nimport type { LLMProvider } from './provider';\nimport { createProvider } from './provider';\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\n // llms 可选——未配置时进入「外部 provider 模式」:工厂照常注册(providers 为空 Map),\n // agent.run/stream 需调用方传 options.provider 才能调用 LLM\n const llms = agentConfig?.llms ?? {};\n if (Object.keys(llms).length === 0) {\n console.log(\n '- @faapi/agent: no llms configured — use agent.run(input, { agent, provider }) to pass an external provider per call',\n );\n }\n\n // 创建 LLM provider 实例 Map(key 是 provider 名,来自 config.agent.llms)\n // 单例,所有请求共享;每个 provider 实例对应一个 LlmConfig\n // 无默认 provider——每次 agent.run/stream 调用经 options.model / options.provider 显式解析\n const providers = new Map<string, LLMProvider>();\n for (const [name, llmConfig] of Object.entries(llms)) {\n // 空 apiKey 照常注册(部分网关/本地模型场景无需 key),但启动日志显性提示——\n // 否则「key 未配置」延迟到首次 LLM 调用才暴露为上游 401,且错误文案来自上游,难排查\n if (!llmConfig.apiKey || llmConfig.apiKey.trim() === '') {\n console.warn(\n `! @faapi/agent: config.agent.llms.${name}.apiKey is empty, requests to this provider will omit Authorization header (upstream likely returns 401)`,\n );\n }\n providers.set(name, createProvider(llmConfig));\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 // tool schema 解析器(setup 闭包级缓存——root + sub-agent 共享)\n // 实现与行为约定见 [toolSchemaResolver.md](./toolSchemaResolver.md)\n const resolveToolSchema = createToolSchemaResolver({ rootDir });\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 llms,\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 Object.keys(llms).length > 0\n ? `- @faapi/agent: providers [${Object.keys(llms).join(', ')}] — call agent.run(input, { agent, model }) to execute (no default agent/provider)`\n : '- @faapi/agent: no llms configured — use agent.run(input, { agent, provider }) to pass an external provider per call',\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": "6.5.0",
3
+ "version": "6.7.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",
@@ -25,12 +25,12 @@
25
25
  "typescript": "^5.7.0",
26
26
  "vitest": "^4.1.11",
27
27
  "zod": "^4.4.3",
28
- "@faapi/faapi": "6.5.0"
28
+ "@faapi/faapi": "6.7.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "typescript": ">=5.7.0",
32
32
  "zod": "^4.4.3",
33
- "@faapi/faapi": "^6.5.0"
33
+ "@faapi/faapi": "^6.7.0"
34
34
  },
35
35
  "peerDependenciesMeta": {
36
36
  "typescript": {