@faapi/agent 3.0.0 → 3.1.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/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  - **LLM Provider 抽象**(Phase 3.2)—— 统一接口对接 OpenAI / Anthropic 等 provider
8
8
  - **ReAct 循环引擎**(Phase 3.3)—— LLM 输出 → 调用 tool / 递归 sub-agent → 把结果回灌给 LLM,循环直到最终回答
9
- - **Agent 类**(Phase 3.4)—— 按 `agent.name` 查找元数据、加载 handler、组装 agent.tools + defaultTools + sub-agent,提供 `run` / `stream` / `asTool`
9
+ - **Agent 类**(Phase 3.4)—— 按 `agent.name` 查找元数据、加载 handler、组装 agent.tools + sub-agent,提供 `run` / `stream` / `asTool`
10
10
  - **递归防护** —— `maxTurns` + `maxAgentDepth`(来自 `config.agent` 或 agent 自身 `config` 块)
11
11
 
12
12
  ## 与 faapi 核心的关系
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { LlmConfig, AgentMetadata, ToolMetadata, ToolModule, AgentModule, AgentToolDescriptor, FaapiPlugin } from '@faapi/faapi';
1
+ import { LlmConfig, AgentToolDescriptor, AgentCore, AgentMetadata, ToolMetadata, ToolModule, AgentModule, FaapiPlugin } from '@faapi/faapi';
2
2
 
3
3
  /**
4
4
  * LLM Provider 错误
@@ -20,7 +20,8 @@ declare class LLMProviderError extends Error {
20
20
  /**
21
21
  * 创建 OpenAI 兼容 LLMProvider
22
22
  *
23
- * @param config LLM 提供方配置(apiKey / model / baseURL / 透传字段)
23
+ * @param config LLM 提供方配置(apiKey / baseURL / models + provider 级透传字段,
24
+ * model 级字段在 `models[modelName]` 下覆盖 provider 级同名字段)
24
25
  * @returns LLMProvider 实例(含 complete + stream 方法)
25
26
  */
26
27
  declare function createOpenAIProvider(config: LlmConfig): LLMProvider;
@@ -170,7 +171,7 @@ interface LLMProvider {
170
171
  *
171
172
  * Phase 3.2 仅支持 `'openai'`,其他值抛错不静默降级(参考 AGENTS.md §6.3)。
172
173
  *
173
- * @param config LLM 提供方配置(来自 faapi.config.ts 的 `agent.llm`)
174
+ * @param config LLM 提供方配置(来自 faapi.config.ts 的 `agent.llms[key]`)
174
175
  * @returns LLMProvider 实例
175
176
  * @throws {Error} 当 `config.provider` 不是已支持的值
176
177
  */
@@ -297,6 +298,110 @@ declare function reactLoop(input: string, config: ReactLoopConfig): Promise<Reac
297
298
  */
298
299
  declare function reactLoopStream(input: string, config: ReactLoopConfig): AsyncIterable<ReactLoopStreamChunk>;
299
300
 
301
+ /**
302
+ * agent handle——注入到 handler 的 `agent` 参数,提供可调用的 agent 运行入口
303
+ *
304
+ * faapi 核心的 [agentHandle](../../faapi/src/injection/agentHandle.md) 工厂注册机制
305
+ * 让本包的 [plugin](./plugin.md) 在 setup 时注册工厂函数,injectParams 在
306
+ * `agent` 参数注入时调工厂拿到 `AgentHandle` 实例。
307
+ *
308
+ * `Agent` 类满足此接口(结构化类型),plugin 的工厂直接返回 `Agent` 实例,
309
+ * 无需额外包装层。handler 通过 `AgentHandle` 类型获得类型安全:
310
+ *
311
+ * ```ts
312
+ * import type { AgentHandle } from '@faapi/agent';
313
+ *
314
+ * // src/api/chat/handler.ts
315
+ * export function POST(agent: AgentHandle, body: { input: string }) {
316
+ * const result = await agent.run(body.input);
317
+ * return { content: result.content, turns: result.turns };
318
+ * }
319
+ * ```
320
+ *
321
+ * 工厂未注册(`@faapi/agent` 插件未加载或 `config.agent.llm` / `defaultAgent` 未配置)
322
+ * 时注入 `undefined`,handler 需自行处理。
323
+ *
324
+ * 详见 [agentHandle.md](./agentHandle.md)。
325
+ */
326
+ /**
327
+ * `agent.run` / `agent.stream` 的 options 参数——临时覆盖本次调用的 LLM 配置
328
+ *
329
+ * 所有字段可选,不传或 `undefined` 时回落到下一优先级(agent 元数据 → 全局配置)。
330
+ * **不修改 agent 自身状态**——下一次调用仍用默认配置。
331
+ *
332
+ * `model` 是字符串 key,支持三种形式(解析规则见 [agentHandle.md](./agentHandle.md) 的
333
+ * 「`options.model` 字符串 key 解析规则」):
334
+ * - llms 的 key 精确匹配(如 `'openai'`)
335
+ * - `provider/model` 一体化(如 `'openai/gpt-4o'`)
336
+ * - 纯 model 名(如 `'gpt-4o'`)—— 在所有 provider 的 `models` 里查找,唯一时切到对应 provider
337
+ *
338
+ * 优先级(高 → 低):`options` > agent 元数据(`config.model` / `config.maxTurns`)> 全局
339
+ * `AgentRuntimeConfig` / `defaultLlm` provider。详见 [agentHandle.md](./agentHandle.md) 的
340
+ * Run-level 覆盖优先级表。
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * // 按请求切模型(纯 model 名,在 llms 里唯一时切到对应 provider)
345
+ * await agent.run(input, { model: 'gpt-4o-mini' });
346
+ *
347
+ * // provider/model 一体化形式(精确切换)
348
+ * await agent.run(input, { model: 'anthropic/claude-3-5-sonnet' });
349
+ * ```
350
+ */
351
+ interface AgentRunOptions {
352
+ /**
353
+ * 切换 provider + model 的字符串 key(支持 llms key / `provider/model` / 纯 model 名)
354
+ *
355
+ * 不传时用 `defaultLlm` provider + agent 元数据 `config.model`。
356
+ */
357
+ model?: string;
358
+ /** 采样温度(透传给 LLM API,覆盖 provider/model 级 temperature) */
359
+ temperature?: number;
360
+ /** 最大生成 token 数(透传给 LLM API) */
361
+ maxTokens?: number;
362
+ }
363
+ interface AgentHandle {
364
+ /**
365
+ * 非流式执行 agent
366
+ *
367
+ * 组装 ReAct 循环 config(systemPrompt + tools + maxTurns + 应用 `options` 覆盖)→ 调
368
+ * [reactLoop](./reactLoop.md) → 返回最终结果。
369
+ *
370
+ * @param input 用户输入文本
371
+ * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens
372
+ * (不修改 agent 自身状态,详见 {@link AgentRunOptions})
373
+ * @returns 循环结果(content + messages + turns + stopReason + usage)
374
+ * @throws {AgentError} agent 未注册
375
+ * @throws {ReactLoopError} 超出 maxTurns
376
+ * @throws {Error} LLM provider 抛错时立即传播
377
+ */
378
+ run(input: string, options?: AgentRunOptions): Promise<ReactLoopResult>;
379
+ /**
380
+ * 流式执行 agent
381
+ *
382
+ * 组装 config(应用 `options` 覆盖)→ 调 [reactLoopStream](./reactLoop.md) → yield 流式 chunk。
383
+ * 适用于 LLM token 流式输出、tool 调用过程展示等场景。
384
+ *
385
+ * @param input 用户输入文本
386
+ * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens
387
+ * (不修改 agent 自身状态,详见 {@link AgentRunOptions})
388
+ * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)
389
+ * @throws {AgentError} agent 未注册
390
+ * @throws {ReactLoopError} 超出 maxTurns
391
+ * @throws {Error} LLM provider 抛错时立即传播
392
+ */
393
+ stream(input: string, options?: AgentRunOptions): AsyncIterable<ReactLoopStreamChunk>;
394
+ /**
395
+ * 把自身包装为 `AgentToolDescriptor` 供 LLM 当 tool 调用
396
+ *
397
+ * 用于 agent-as-tool 场景:父 agent 把子 agent 包装为 tool,
398
+ * 加入 LLM 可见 tool 列表,LLM 调用时触发 sub-agent 递归执行。
399
+ *
400
+ * @returns `AgentToolDescriptor` 或 `undefined`(agent 未注册)
401
+ */
402
+ asTool(): AgentToolDescriptor | undefined;
403
+ }
404
+
300
405
  /**
301
406
  * 全局 agent 配置覆盖
302
407
  *
@@ -308,8 +413,6 @@ interface AgentRuntimeConfig {
308
413
  maxTurns?: number;
309
414
  /** agent 调用 agent 的最大递归深度(默认 3) */
310
415
  maxAgentDepth?: number;
311
- /** 默认 tool 列表,所有 agent 都可用 */
312
- defaultTools?: string[];
313
416
  }
314
417
  /**
315
418
  * tool schema 解析结果
@@ -342,26 +445,34 @@ interface ToolSchemaResolution {
342
445
  * 访问器签名与 faapi 核心对称(见 [agent.md](./agent.md) 依赖注入章节)。
343
446
  */
344
447
  interface AgentDeps {
345
- /** LLM provider 实例 */
346
- provider: LLMProvider;
448
+ /** LLM provider 实例映射(key 是 provider 名,来自 config.agent.llms) */
449
+ providers: Map<string, LLMProvider>;
450
+ /** 默认 provider 实例(config.agent.defaultLlm 对应,或 llms 第一个 key) */
451
+ defaultProvider: LLMProvider;
452
+ /** LLM provider 配置映射(含 models,用于 options.model key 解析) */
453
+ llms: Record<string, LlmConfig>;
454
+ /** 默认 provider key(config.agent.defaultLlm,或 llms 第一个 key) */
455
+ defaultLlm: string;
347
456
  /** 当前 agent 名 */
348
457
  agentName: string;
349
458
  /** 项目根目录(Phase 3.5 接线时用于加载器) */
350
459
  rootDir: string;
351
460
  /** 全局 agent 配置覆盖 */
352
461
  config?: AgentRuntimeConfig;
353
- /** 查 agent 元数据(对应 agentRegistry.getAgent) */
354
- getAgent: (name: string) => AgentMetadata | undefined;
462
+ /** 查 agent LLM 可见元数据(对应 agentRegistry.getAgent,返回 AgentCore) */
463
+ getAgent: (name: string) => AgentCore | undefined;
464
+ /** 查 agent 完整元数据(对应 agentRegistry.getAgentEntry,返回 AgentMetadata 含 filePath/hasRun) */
465
+ getAgentEntry: (name: string) => AgentMetadata | undefined;
355
466
  /** 查 tool 元数据(对应 toolRegistry.getTool) */
356
467
  getTool: (name: string) => ToolMetadata | undefined;
357
468
  /** 解析 agent 可用常规 tool(对应 agentRegistry.resolveAgentTools) */
358
469
  resolveAgentTools: (name: string) => ToolMetadata[];
359
- /** 解析 agent 可调用 sub-agent 列表(对应 agentRegistry.resolveSubAgents) */
360
- resolveSubAgents: (name: string) => AgentMetadata[];
470
+ /** 解析 agent 可调用 sub-agent 列表(对应 agentRegistry.resolveSubAgents,返回 AgentCore[]) */
471
+ resolveSubAgents: (name: string) => AgentCore[];
361
472
  /** 动态 import tool handler(对应 loadToolModule) */
362
473
  loadToolModule: (filePath: string, functionName: string) => Promise<ToolModule>;
363
- /** 动态 import agent handler(对应 loadAgentModule) */
364
- loadAgentModule: (filePath: string, hasConfig: boolean, hasRun: boolean) => Promise<AgentModule>;
474
+ /** 动态 import agent handler(对应 loadAgentModule,仅 hasRun 参数,无 hasConfig) */
475
+ loadAgentModule: (filePath: string, hasRun: boolean) => Promise<AgentModule>;
365
476
  /** tool input 的 schema 解析(Phase 3.5 实现,可选) */
366
477
  resolveToolSchema?: (tool: ToolMetadata) => Promise<ToolSchemaResolution | undefined>;
367
478
  }
@@ -406,7 +517,7 @@ declare class Agent {
406
517
  */
407
518
  private readonly schemaCache;
408
519
  /**
409
- * @param deps 运行时依赖(访问器 + provider + config)
520
+ * @param deps 运行时依赖(访问器 + providers Map + defaultProvider + llms + config)
410
521
  * @param depth 递归深度(默认 1 = 根 agent;sub-agent 递归时传入 depth+1)
411
522
  */
412
523
  constructor(deps: AgentDeps, depth?: number);
@@ -414,22 +525,26 @@ declare class Agent {
414
525
  * 非流式执行——组装 config 调 [reactLoop](./reactLoop.md)
415
526
  *
416
527
  * @param input 用户输入
528
+ * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens
529
+ * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))
417
530
  * @returns 最终结果(content + messages + turns + stopReason + usage)
418
531
  * @throws {AgentError} agent 未注册
419
532
  * @throws {ReactLoopError} 超出 maxTurns
420
533
  * @throws {Error} provider.complete 抛错时立即传播
421
534
  */
422
- run(input: string): Promise<ReactLoopResult>;
535
+ run(input: string, options?: AgentRunOptions): Promise<ReactLoopResult>;
423
536
  /**
424
537
  * 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)
425
538
  *
426
539
  * @param input 用户输入
540
+ * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens
541
+ * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))
427
542
  * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)
428
543
  * @throws {AgentError} agent 未注册
429
544
  * @throws {ReactLoopError} 超出 maxTurns
430
545
  * @throws {Error} provider.stream 抛错时立即传播
431
546
  */
432
- stream(input: string): AsyncIterable<ReactLoopStreamChunk>;
547
+ stream(input: string, options?: AgentRunOptions): AsyncIterable<ReactLoopStreamChunk>;
433
548
  /**
434
549
  * 把自身包装为 `AgentToolDescriptor` 供 LLM 当 tool 调用
435
550
  *
@@ -452,18 +567,39 @@ declare class Agent {
452
567
  /**
453
568
  * 组装 ReactLoopConfig
454
569
  *
455
- * 1. 查 agent 元数据(未注册抛 AgentError
570
+ * 1. 查 agent 元数据(未注册抛 AgentError)——用 `getAgent` 拿 AgentCore
571
+ * (LLM-facing 字段:systemPrompt / model / maxTurns)
456
572
  * 2. buildToolDefinitions 组装 tool 列表
457
- * 3. config 字段优先级:agent 元数据 > 全局 AgentRuntimeConfig
573
+ * 3. config 字段优先级(高 → 低):`options` > agent 元数据 > 全局 AgentRuntimeConfig / deps.defaultProvider
574
+ *
575
+ * `options.model` 是字符串 key,由 {@link resolveModelKey} 解析为 provider + model
576
+ * (支持 llms key 精确匹配 / `provider/model` 一体化 / 纯 model 名模糊匹配)。
577
+ * 不传 `options.model` 时用 `deps.defaultProvider` + agent 元数据 `config.model`。
578
+ * 详见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。
458
579
  */
459
580
  private buildLoopConfig;
581
+ /**
582
+ * 解析 `options.model` 字符串 key → provider + model
583
+ *
584
+ * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」:
585
+ * 1. `undefined` → `deps.defaultProvider` + `meta.model`
586
+ * 2. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key
587
+ * 3. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model
588
+ * (要求该 model 在 `deps.llms[provider].models` 里)
589
+ * 4. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找
590
+ * - 唯一 → 该 provider + 该 model
591
+ * - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)
592
+ * - 无 → 抛 `AgentError`
593
+ *
594
+ * @throws {AgentError} key 解析失败(provider/model 不存在或歧义)
595
+ */
596
+ private resolveModelKey;
460
597
  /**
461
598
  * 组装 LLM 可见 tool 列表
462
599
  *
463
- * 合并三个来源(按 `name` 去重,先入者保留):
600
+ * 合并两个来源(按 `name` 去重,先入者保留):
464
601
  * 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用
465
- * 2. **全局 defaultTools** —— `config.defaultTools` 中的 tool 名(所有 agent 共享)
466
- * 3. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`
602
+ * 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`
467
603
  *
468
604
  * 每个常规 tool 的 `input`:
469
605
  * - `resolveToolSchema` 提供 → 用其 `jsonSchema`
@@ -493,71 +629,12 @@ declare class Agent {
493
629
  *
494
630
  * 自定义 run 接收原始 args 对象;默认 reactLoop 接收 stringify 后的 args
495
631
  * 作为 user 消息(agent-as-tool input 为开放式 JSON)。
496
- */
497
- private executeSubAgent;
498
- }
499
-
500
- /**
501
- * agent handle——注入到 handler 的 `agent` 参数,提供可调用的 agent 运行入口
502
- *
503
- * faapi 核心的 [agentHandle](../../faapi/src/injection/agentHandle.md) 工厂注册机制
504
- * 让本包的 [plugin](./plugin.md) 在 setup 时注册工厂函数,injectParams 在
505
- * `agent` 参数注入时调工厂拿到 `AgentHandle` 实例。
506
- *
507
- * `Agent` 类满足此接口(结构化类型),plugin 的工厂直接返回 `Agent` 实例,
508
- * 无需额外包装层。handler 通过 `AgentHandle` 类型获得类型安全:
509
- *
510
- * ```ts
511
- * import type { AgentHandle } from '@faapi/agent';
512
- *
513
- * // src/api/chat/handler.ts
514
- * export function POST(agent: AgentHandle, body: { input: string }) {
515
- * const result = await agent.run(body.input);
516
- * return { content: result.content, turns: result.turns };
517
- * }
518
- * ```
519
- *
520
- * 工厂未注册(`@faapi/agent` 插件未加载或 `config.agent.llm` / `defaultAgent` 未配置)
521
- * 时注入 `undefined`,handler 需自行处理。
522
- *
523
- * 详见 [agentHandle.md](./agentHandle.md)。
524
- */
525
- interface AgentHandle {
526
- /**
527
- * 非流式执行 agent
528
- *
529
- * 组装 ReAct 循环 config(systemPrompt + tools + maxTurns)→ 调
530
- * [reactLoop](./reactLoop.md) → 返回最终结果。
531
632
  *
532
- * @param input 用户输入文本
533
- * @returns 循环结果(content + messages + turns + stopReason + usage)
534
- * @throws {AgentError} agent 未注册
535
- * @throws {ReactLoopError} 超出 maxTurns
536
- * @throws {Error} LLM provider 抛错时立即传播
633
+ * 加载 handler.js 用 `getAgentEntry`(返回 AgentMetadata,含 filePath/hasRun),
634
+ * 而非 `getAgent`(返回 AgentCore,无代码加载细节)。DB skill 无文件,
635
+ * `getAgentEntry` 返回 `undefined`,走默认 reactLoop。
537
636
  */
538
- run(input: string): Promise<ReactLoopResult>;
539
- /**
540
- * 流式执行 agent
541
- *
542
- * 组装 config → 调 [reactLoopStream](./reactLoop.md) → yield 流式 chunk。
543
- * 适用于 LLM token 流式输出、tool 调用过程展示等场景。
544
- *
545
- * @param input 用户输入文本
546
- * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)
547
- * @throws {AgentError} agent 未注册
548
- * @throws {ReactLoopError} 超出 maxTurns
549
- * @throws {Error} LLM provider 抛错时立即传播
550
- */
551
- stream(input: string): AsyncIterable<ReactLoopStreamChunk>;
552
- /**
553
- * 把自身包装为 `AgentToolDescriptor` 供 LLM 当 tool 调用
554
- *
555
- * 用于 agent-as-tool 场景:父 agent 把子 agent 包装为 tool,
556
- * 加入 LLM 可见 tool 列表,LLM 调用时触发 sub-agent 递归执行。
557
- *
558
- * @returns `AgentToolDescriptor` 或 `undefined`(agent 未注册)
559
- */
560
- asTool(): AgentToolDescriptor | undefined;
637
+ private executeSubAgent;
561
638
  }
562
639
 
563
640
  /**
@@ -569,7 +646,15 @@ interface AgentHandle {
569
646
  *
570
647
  * export default {
571
648
  * agent: {
572
- * llm: { provider: 'openai', apiKey: process.env.OPENAI_API_KEY, model: 'gpt-4o' },
649
+ * llms: {
650
+ * openai: {
651
+ * provider: 'openai',
652
+ * apiKey: process.env.OPENAI_API_KEY,
653
+ * baseURL: 'https://api.openai.com/v1',
654
+ * models: { 'gpt-4o': {}, 'gpt-4o-mini': { temperature: 0.5 } },
655
+ * },
656
+ * },
657
+ * defaultLlm: 'openai',
573
658
  * defaultAgent: 'researcher',
574
659
  * maxTurns: 10,
575
660
  * },
@@ -578,14 +663,15 @@ interface AgentHandle {
578
663
  * ```
579
664
  *
580
665
  * 插件 setup 时:
581
- * 1. `config.agent.llm` → `createProvider` → LLMProvider 实例(单例)
582
- * 2. 读 `config.agent.defaultAgent` / `maxTurns` / `maxAgentDepth` / `defaultTools`
583
- * 3. `@faapi/faapi` import 注册表/加载器访问器(getAgent / getTool / resolveAgentTools /
666
+ * 1. 遍历 `config.agent.llms` → 每项调 `createProvider` → `Map<providerKey, LLMProvider>`
667
+ * 2. 读 `config.agent.defaultLlm` `defaultProvider`(未设时用 `llms` 第一个 key)
668
+ * 3. `config.agent.defaultAgent` / `maxTurns` / `maxAgentDepth`
669
+ * 4. 从 `@faapi/faapi` import 注册表/加载器访问器(getAgent / getTool / resolveAgentTools /
584
670
  * resolveSubAgents / loadAgentModule / loadToolModule)
585
- * 4. `registerAgentHandleFactory` 注册工厂——每次请求时构造 [Agent](./agent.md) 实例注入到
671
+ * 5. `registerAgentHandleFactory` 注册工厂——每次请求时构造 [Agent](./agent.md) 实例注入到
586
672
  * handler 的 `agent` 参数
587
673
  *
588
- * 配置缺失时(`agent.llm` 或 `agent.defaultAgent` 未设置)跳过工厂注册并打印警告,
674
+ * 配置缺失时(`agent.llms` 或 `agent.defaultAgent` 未设置)跳过工厂注册并打印警告,
589
675
  * handler 的 `agent` 参数注入 `undefined`。
590
676
  *
591
677
  * 详见 [plugin.md](./plugin.md)。
@@ -599,4 +685,4 @@ interface AgentHandle {
599
685
  */
600
686
  declare const agentPlugin: FaapiPlugin;
601
687
 
602
- export { Agent, type AgentDeps, AgentError, type AgentHandle, AgentRecursionError, type AgentRuntimeConfig, type LLMCompleteRequest, type LLMMessage, type LLMProvider, LLMProviderError, type LLMResponse, type LLMStopReason, type LLMStreamChunk, type LLMToolCall, type LLMToolDefinition, type LLMUsage, type ReactLoopConfig, ReactLoopError, type ReactLoopResult, type ReactLoopStreamChunk, type ToolExecutor, type ToolSchemaResolution, createOpenAIProvider, createProvider, agentPlugin as default, reactLoop, reactLoopStream };
688
+ export { Agent, type AgentDeps, AgentError, type AgentHandle, AgentRecursionError, type AgentRunOptions, type AgentRuntimeConfig, type LLMCompleteRequest, type LLMMessage, type LLMProvider, LLMProviderError, type LLMResponse, type LLMStopReason, type LLMStreamChunk, type LLMToolCall, type LLMToolDefinition, type LLMUsage, type ReactLoopConfig, ReactLoopError, type ReactLoopResult, type ReactLoopStreamChunk, type ToolExecutor, type ToolSchemaResolution, createOpenAIProvider, createProvider, agentPlugin as default, reactLoop, reactLoopStream };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/providers/openai.ts
2
2
  var DEFAULT_BASE_URL = "https://api.openai.com/v1";
3
- var RESERVED_CONFIG_KEYS = /* @__PURE__ */ new Set(["provider", "apiKey", "model", "baseURL"]);
3
+ var RESERVED_CONFIG_KEYS = /* @__PURE__ */ new Set(["provider", "apiKey", "model", "baseURL", "models"]);
4
4
  var LLMProviderError = class extends Error {
5
5
  /** HTTP 状态码(网络错误 / JSON 解析错误为 undefined) */
6
6
  status;
@@ -17,22 +17,40 @@ function createOpenAIProvider(config) {
17
17
  const baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
18
18
  const apiKey = config.apiKey;
19
19
  function buildRequestBody(request) {
20
+ const modelName = request.model ?? Object.keys(config.models)[0];
21
+ const modelConfig = modelName ? config.models[modelName] : void 0;
20
22
  const body = {
21
- model: request.model ?? config.model,
23
+ model: modelName,
22
24
  messages: request.messages.map(toOpenAIMessage)
23
25
  };
24
26
  if (request.tools && request.tools.length > 0) {
25
27
  body.tools = request.tools.map(toOpenAITool);
26
28
  }
27
- if (request.temperature !== void 0) body.temperature = request.temperature;
28
- if (request.maxTokens !== void 0) body.max_tokens = request.maxTokens;
29
+ const mergedConfig = {};
29
30
  for (const key of Object.keys(config)) {
30
31
  if (RESERVED_CONFIG_KEYS.has(key)) continue;
31
32
  const value = config[key];
33
+ if (value !== void 0) {
34
+ mergedConfig[key] = value;
35
+ }
36
+ }
37
+ if (modelConfig) {
38
+ for (const key of Object.keys(modelConfig)) {
39
+ if (RESERVED_CONFIG_KEYS.has(key)) continue;
40
+ const value = modelConfig[key];
41
+ if (value !== void 0) {
42
+ mergedConfig[key] = value;
43
+ }
44
+ }
45
+ }
46
+ for (const key of Object.keys(mergedConfig)) {
47
+ const value = mergedConfig[key];
32
48
  if (value !== void 0 && !(key in body)) {
33
49
  body[key] = value;
34
50
  }
35
51
  }
52
+ if (request.temperature !== void 0) body.temperature = request.temperature;
53
+ if (request.maxTokens !== void 0) body.max_tokens = request.maxTokens;
36
54
  return body;
37
55
  }
38
56
  function buildHeaders() {
@@ -496,7 +514,7 @@ var Agent = class _Agent {
496
514
  */
497
515
  schemaCache = /* @__PURE__ */ new Map();
498
516
  /**
499
- * @param deps 运行时依赖(访问器 + provider + config)
517
+ * @param deps 运行时依赖(访问器 + providers Map + defaultProvider + llms + config)
500
518
  * @param depth 递归深度(默认 1 = 根 agent;sub-agent 递归时传入 depth+1)
501
519
  */
502
520
  constructor(deps, depth = 1) {
@@ -507,26 +525,30 @@ var Agent = class _Agent {
507
525
  * 非流式执行——组装 config 调 [reactLoop](./reactLoop.md)
508
526
  *
509
527
  * @param input 用户输入
528
+ * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens
529
+ * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))
510
530
  * @returns 最终结果(content + messages + turns + stopReason + usage)
511
531
  * @throws {AgentError} agent 未注册
512
532
  * @throws {ReactLoopError} 超出 maxTurns
513
533
  * @throws {Error} provider.complete 抛错时立即传播
514
534
  */
515
- async run(input) {
516
- const config = await this.buildLoopConfig();
535
+ async run(input, options) {
536
+ const config = await this.buildLoopConfig(options);
517
537
  return reactLoop(input, config);
518
538
  }
519
539
  /**
520
540
  * 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)
521
541
  *
522
542
  * @param input 用户输入
543
+ * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens
544
+ * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))
523
545
  * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)
524
546
  * @throws {AgentError} agent 未注册
525
547
  * @throws {ReactLoopError} 超出 maxTurns
526
548
  * @throws {Error} provider.stream 抛错时立即传播
527
549
  */
528
- async *stream(input) {
529
- const config = await this.buildLoopConfig();
550
+ async *stream(input, options) {
551
+ const config = await this.buildLoopConfig(options);
530
552
  yield* reactLoopStream(input, config);
531
553
  }
532
554
  /**
@@ -570,32 +592,100 @@ var Agent = class _Agent {
570
592
  /**
571
593
  * 组装 ReactLoopConfig
572
594
  *
573
- * 1. 查 agent 元数据(未注册抛 AgentError
595
+ * 1. 查 agent 元数据(未注册抛 AgentError)——用 `getAgent` 拿 AgentCore
596
+ * (LLM-facing 字段:systemPrompt / model / maxTurns)
574
597
  * 2. buildToolDefinitions 组装 tool 列表
575
- * 3. config 字段优先级:agent 元数据 > 全局 AgentRuntimeConfig
598
+ * 3. config 字段优先级(高 → 低):`options` > agent 元数据 > 全局 AgentRuntimeConfig / deps.defaultProvider
599
+ *
600
+ * `options.model` 是字符串 key,由 {@link resolveModelKey} 解析为 provider + model
601
+ * (支持 llms key 精确匹配 / `provider/model` 一体化 / 纯 model 名模糊匹配)。
602
+ * 不传 `options.model` 时用 `deps.defaultProvider` + agent 元数据 `config.model`。
603
+ * 详见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。
576
604
  */
577
- async buildLoopConfig() {
605
+ async buildLoopConfig(options) {
578
606
  const meta = this.deps.getAgent(this.deps.agentName);
579
607
  if (!meta) {
580
608
  throw new AgentError(`Agent "${this.deps.agentName}" is not registered`);
581
609
  }
582
610
  const tools = await this.buildToolDefinitions();
611
+ const { provider, model } = this.resolveModelKey(options?.model, meta);
583
612
  return {
584
- provider: this.deps.provider,
613
+ provider,
585
614
  systemPrompt: meta.systemPrompt,
586
- model: meta.model,
615
+ model,
616
+ temperature: options?.temperature,
617
+ maxTokens: options?.maxTokens,
587
618
  maxTurns: meta.maxTurns ?? this.deps.config?.maxTurns,
588
619
  tools,
589
620
  executeTool: async (name, args) => this.executeTool(name, args)
590
621
  };
591
622
  }
623
+ /**
624
+ * 解析 `options.model` 字符串 key → provider + model
625
+ *
626
+ * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」:
627
+ * 1. `undefined` → `deps.defaultProvider` + `meta.model`
628
+ * 2. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key
629
+ * 3. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model
630
+ * (要求该 model 在 `deps.llms[provider].models` 里)
631
+ * 4. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找
632
+ * - 唯一 → 该 provider + 该 model
633
+ * - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)
634
+ * - 无 → 抛 `AgentError`
635
+ *
636
+ * @throws {AgentError} key 解析失败(provider/model 不存在或歧义)
637
+ */
638
+ resolveModelKey(key, meta) {
639
+ if (key === void 0) {
640
+ return { provider: this.deps.defaultProvider, model: meta.model };
641
+ }
642
+ const byProviderKey = this.deps.providers.get(key);
643
+ if (byProviderKey) {
644
+ const llmConfig = this.deps.llms[key];
645
+ const firstModel = llmConfig ? Object.keys(llmConfig.models)[0] : void 0;
646
+ return { provider: byProviderKey, model: firstModel ?? meta.model };
647
+ }
648
+ if (key.includes("/")) {
649
+ const slashIdx = key.indexOf("/");
650
+ const providerName = key.slice(0, slashIdx);
651
+ const modelName = key.slice(slashIdx + 1);
652
+ const provider = this.deps.providers.get(providerName);
653
+ if (!provider) {
654
+ throw new AgentError(`Unknown provider "${providerName}" in model key "${key}"`);
655
+ }
656
+ const llmConfig = this.deps.llms[providerName];
657
+ if (!llmConfig || !llmConfig.models[modelName]) {
658
+ throw new AgentError(
659
+ `Model "${modelName}" not found in provider "${providerName}". Declare it in config.agent.llms.${providerName}.models.`
660
+ );
661
+ }
662
+ return { provider, model: modelName };
663
+ }
664
+ const matches = [];
665
+ for (const [providerName, provider] of this.deps.providers) {
666
+ const llmConfig = this.deps.llms[providerName];
667
+ if (llmConfig && llmConfig.models[key]) {
668
+ matches.push({ provider, providerName });
669
+ }
670
+ }
671
+ if (matches.length === 1) {
672
+ return { provider: matches[0].provider, model: key };
673
+ }
674
+ if (matches.length > 1) {
675
+ throw new AgentError(
676
+ `Model "${key}" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(", ")}). Use "provider/model" to disambiguate.`
677
+ );
678
+ }
679
+ throw new AgentError(
680
+ `Model "${key}" not found in any provider. Declare it in config.agent.llms.*.models.`
681
+ );
682
+ }
592
683
  /**
593
684
  * 组装 LLM 可见 tool 列表
594
685
  *
595
- * 合并三个来源(按 `name` 去重,先入者保留):
686
+ * 合并两个来源(按 `name` 去重,先入者保留):
596
687
  * 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用
597
- * 2. **全局 defaultTools** —— `config.defaultTools` 中的 tool 名(所有 agent 共享)
598
- * 3. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`
688
+ * 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`
599
689
  *
600
690
  * 每个常规 tool 的 `input`:
601
691
  * - `resolveToolSchema` 提供 → 用其 `jsonSchema`
@@ -614,20 +704,6 @@ var Agent = class _Agent {
614
704
  input: schemaRes?.jsonSchema ?? { type: "object" }
615
705
  });
616
706
  }
617
- const defaultTools = this.deps.config?.defaultTools;
618
- if (defaultTools) {
619
- for (const toolName of defaultTools) {
620
- if (definitions.has(toolName)) continue;
621
- const tool = this.deps.getTool(toolName);
622
- if (!tool) continue;
623
- const schemaRes = await this.getToolSchema(tool);
624
- definitions.set(tool.name, {
625
- name: tool.name,
626
- description: tool.description,
627
- input: schemaRes?.jsonSchema ?? { type: "object" }
628
- });
629
- }
630
- }
631
707
  for (const subAgent of this.deps.resolveSubAgents(this.deps.agentName)) {
632
708
  const name = `agent.${subAgent.name}`;
633
709
  if (definitions.has(name)) continue;
@@ -679,6 +755,10 @@ var Agent = class _Agent {
679
755
  *
680
756
  * 自定义 run 接收原始 args 对象;默认 reactLoop 接收 stringify 后的 args
681
757
  * 作为 user 消息(agent-as-tool input 为开放式 JSON)。
758
+ *
759
+ * 加载 handler.js 用 `getAgentEntry`(返回 AgentMetadata,含 filePath/hasRun),
760
+ * 而非 `getAgent`(返回 AgentCore,无代码加载细节)。DB skill 无文件,
761
+ * `getAgentEntry` 返回 `undefined`,走默认 reactLoop。
682
762
  */
683
763
  async executeSubAgent(subName, args) {
684
764
  const newDepth = this.depth + 1;
@@ -688,9 +768,9 @@ var Agent = class _Agent {
688
768
  }
689
769
  const subDeps = { ...this.deps, agentName: subName };
690
770
  const subAgent = new _Agent(subDeps, newDepth);
691
- const meta = this.deps.getAgent(subName);
692
- if (meta?.hasRun) {
693
- const mod = await this.deps.loadAgentModule(meta.filePath, meta.hasConfig, meta.hasRun);
771
+ const entry = this.deps.getAgentEntry(subName);
772
+ if (entry?.hasRun) {
773
+ const mod = await this.deps.loadAgentModule(entry.filePath, entry.hasRun);
694
774
  if (mod.run) {
695
775
  return await mod.run(args);
696
776
  }
@@ -704,6 +784,7 @@ var Agent = class _Agent {
704
784
  import {
705
785
  registerAgentHandleFactory,
706
786
  getAgent,
787
+ getAgentEntry,
707
788
  getTool,
708
789
  resolveAgentTools,
709
790
  resolveSubAgents,
@@ -736,9 +817,9 @@ var agentPlugin = {
736
817
  name: "@faapi/agent",
737
818
  setup(ctx) {
738
819
  const agentConfig = readAgentConfig(ctx);
739
- if (!agentConfig?.llm) {
820
+ if (!agentConfig?.llms) {
740
821
  console.warn(
741
- "! @faapi/agent: config.agent.llm not configured, agent parameter injection disabled"
822
+ "! @faapi/agent: config.agent.llms not configured, agent parameter injection disabled"
742
823
  );
743
824
  return;
744
825
  }
@@ -748,36 +829,52 @@ var agentPlugin = {
748
829
  );
749
830
  return;
750
831
  }
751
- const provider = createProvider(agentConfig.llm);
832
+ const llms = agentConfig.llms;
833
+ const providers = /* @__PURE__ */ new Map();
834
+ for (const [name, llmConfig] of Object.entries(llms)) {
835
+ providers.set(name, createProvider(llmConfig));
836
+ }
837
+ const defaultLlm = agentConfig.defaultLlm ?? Object.keys(llms)[0];
838
+ const defaultProvider = providers.get(defaultLlm);
839
+ if (!defaultProvider) {
840
+ console.warn(
841
+ `! @faapi/agent: config.agent.defaultLlm "${defaultLlm}" not found in llms, agent parameter injection disabled`
842
+ );
843
+ return;
844
+ }
752
845
  const runtimeConfig = {
753
846
  maxTurns: agentConfig.maxTurns,
754
- maxAgentDepth: agentConfig.maxAgentDepth,
755
- defaultTools: agentConfig.defaultTools
847
+ maxAgentDepth: agentConfig.maxAgentDepth
756
848
  };
757
849
  const rootDir = ctx.rootDir;
758
850
  const defaultAgent = agentConfig.defaultAgent;
759
851
  const resolveToolSchema = (tool) => resolveToolSchemaImpl(tool, rootDir);
760
852
  registerAgentHandleFactory(() => {
761
853
  return new Agent({
762
- provider,
854
+ providers,
855
+ defaultProvider,
856
+ llms,
857
+ defaultLlm,
763
858
  agentName: defaultAgent,
764
859
  rootDir,
765
860
  config: runtimeConfig,
766
861
  // 注册表/加载器访问器——从 @faapi/faapi import 的单例模块
767
862
  // createAppBase 启动时已水合 agentRegistry / toolRegistry
863
+ // getAgent 返回 AgentCore(LLM-facing);getAgentEntry 返回 AgentMetadata(含 filePath/hasRun,供加载 handler.js)
768
864
  getAgent,
865
+ getAgentEntry,
769
866
  getTool,
770
867
  resolveAgentTools,
771
868
  resolveSubAgents,
772
869
  // 加载器包装:注入 rootDir 用于 dev 按需编译模式
773
870
  loadToolModule: (filePath, functionName) => loadToolModule(filePath, functionName, rootDir),
774
- loadAgentModule: (filePath, hasConfig, hasRun) => loadAgentModule(filePath, hasConfig, hasRun, rootDir),
871
+ loadAgentModule: (filePath, hasRun) => loadAgentModule(filePath, hasRun, rootDir),
775
872
  // tool schema 解析(zod.js → JSON Schema + safeParse 校验)
776
873
  resolveToolSchema
777
874
  });
778
875
  });
779
876
  console.log(
780
- `- @faapi/agent: default agent "${defaultAgent}" available via agent parameter injection`
877
+ `- @faapi/agent: default agent "${defaultAgent}" (provider: ${defaultLlm}) available via agent parameter injection`
781
878
  );
782
879
  }
783
880
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/providers/openai.ts","../src/provider.ts","../src/reactLoop.ts","../src/agent.ts","../src/plugin.ts"],"sourcesContent":["import type { LlmConfig } from '@faapi/faapi';\nimport type {\n LLMCompleteRequest,\n LLMMessage,\n LLMProvider,\n LLMResponse,\n LLMStopReason,\n LLMStreamChunk,\n LLMToolCall,\n LLMToolDefinition,\n LLMUsage,\n} from '../provider';\n\n/**\n * OpenAI 兼容 API 的 LLMProvider 实现\n *\n * 通过 `LlmConfig.baseURL` 可指向任意 OpenAI 兼容中转服务\n * (官方 OpenAI / Azure OpenAI / LiteLLM / one-api / 自建网关)。\n *\n * 详见 [openai.md](./openai.md)。\n */\n\n/** OpenAI 默认 API 端点(未设 baseURL 时使用) */\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\n\n/** OpenAI 已知透传字段集合(这些字段在 LlmConfig 中有专门处理,不算\"额外透传\") */\nconst RESERVED_CONFIG_KEYS = new Set(['provider', 'apiKey', 'model', 'baseURL']);\n\n/**\n * LLM Provider 错误\n *\n * 含 HTTP 状态码(网络错误为 `undefined`)和响应体摘要,\n * 业务方可通过 `instanceof LLMProviderError` 区分 LLM 错误与其他错误。\n */\nexport class LLMProviderError extends Error {\n /** HTTP 状态码(网络错误 / JSON 解析错误为 undefined) */\n readonly status?: number;\n /** 响应体摘要(前 500 字符,便于诊断) */\n readonly body?: string;\n\n constructor(message: string, options?: { status?: number; body?: string; cause?: unknown }) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = 'LLMProviderError';\n this.status = options?.status;\n this.body = options?.body;\n }\n}\n\n/** 内部存储:OpenAI chat completions 请求体(构造阶段) */\ninterface OpenAIRequestBody {\n model?: string;\n messages: unknown[];\n tools?: unknown[];\n stream?: boolean;\n temperature?: number;\n max_tokens?: number;\n [key: string]: unknown;\n}\n\n/** 内部存储:OpenAI tool_call 项(非流式 + 流式统一,index 仅流式有) */\ninterface OpenAIToolCall {\n index?: number;\n id?: string;\n type?: string;\n function?: { name?: string; arguments?: string };\n}\n\n/** 内部存储:OpenAI usage 字段 */\ninterface OpenAIUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}\n\n/** 内部存储:OpenAI chat completions 响应 */\ninterface OpenAIResponseJson {\n choices?: Array<{\n index?: number;\n message?: {\n role?: string;\n content?: string | null;\n tool_calls?: OpenAIToolCall[];\n };\n finish_reason?: string | null;\n }>;\n usage?: OpenAIUsage;\n}\n\n/** 内部存储:OpenAI streaming chunk */\ninterface OpenAIStreamChunkJson {\n choices?: Array<{\n delta?: {\n content?: string | null;\n tool_calls?: OpenAIToolCall[];\n };\n finish_reason?: string | null;\n }>;\n usage?: OpenAIUsage;\n}\n\n/** tool call 累积器(stream 模式按 index 累积 args 字符串) */\ninterface ToolCallAccumulator {\n id?: string;\n name?: string;\n argsString: string;\n}\n\n/**\n * 创建 OpenAI 兼容 LLMProvider\n *\n * @param config LLM 提供方配置(apiKey / model / baseURL / 透传字段)\n * @returns LLMProvider 实例(含 complete + stream 方法)\n */\nexport function createOpenAIProvider(config: LlmConfig): LLMProvider {\n const baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const apiKey = config.apiKey;\n\n /** 构造 OpenAI chat completions 请求体 */\n function buildRequestBody(request: LLMCompleteRequest): OpenAIRequestBody {\n const body: OpenAIRequestBody = {\n model: request.model ?? config.model,\n messages: request.messages.map(toOpenAIMessage),\n };\n\n if (request.tools && request.tools.length > 0) {\n body.tools = request.tools.map(toOpenAITool);\n }\n\n if (request.temperature !== undefined) body.temperature = request.temperature;\n if (request.maxTokens !== undefined) body.max_tokens = request.maxTokens;\n\n // 透传 config 额外字段(temperature / top_p / max_tokens 等,业务方在 LlmConfig 设)\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 && !(key in body)) {\n body[key] = value;\n }\n }\n\n return body;\n }\n\n /** 构造请求 headers */\n function buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (apiKey) headers.Authorization = `Bearer ${apiKey}`;\n return headers;\n }\n\n /** 调 fetch,统一捕获网络错误 */\n async function safeFetch(url: string, init: RequestInit): Promise<Response> {\n try {\n return await fetch(url, init);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new LLMProviderError(`Network error: ${reason}`, { cause: err });\n }\n }\n\n /** 校验 HTTP 响应状态,非 2xx 抛 LLMProviderError */\n async function ensureOk(response: Response): Promise<string> {\n if (response.ok) return '';\n const bodyText = await response.text();\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError(`HTTP ${response.status}: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n // ─── complete ──────────────────────────────────────\n\n async function complete(request: LLMCompleteRequest): Promise<LLMResponse> {\n const url = `${baseURL}/chat/completions`;\n const body = buildRequestBody(request);\n const init: RequestInit = {\n method: 'POST',\n headers: buildHeaders(),\n body: JSON.stringify(body),\n };\n\n const response = await safeFetch(url, init);\n await ensureOk(response);\n\n const bodyText = await response.text();\n let json: OpenAIResponseJson;\n try {\n json = JSON.parse(bodyText) as OpenAIResponseJson;\n } catch {\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError(`Invalid JSON response: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n const choice = json.choices?.[0];\n const msg = choice?.message;\n if (!choice || !msg) {\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError('Empty choices in response', {\n status: response.status,\n body: excerpt,\n });\n }\n\n const content = msg.content ?? '';\n const toolCalls = parseToolCalls(msg.tool_calls, bodyText, response.status);\n\n return {\n message: {\n role: 'assistant',\n content,\n toolCalls,\n },\n stopReason: mapStopReason(choice.finish_reason ?? undefined),\n usage: mapUsage(json.usage),\n };\n }\n\n // ─── stream ──────────────────────────────────────\n\n async function* stream(request: LLMCompleteRequest): AsyncIterable<LLMStreamChunk> {\n const url = `${baseURL}/chat/completions`;\n const body = buildRequestBody(request);\n body.stream = true;\n\n const init: RequestInit = {\n method: 'POST',\n headers: buildHeaders(),\n body: JSON.stringify(body),\n };\n\n const response = await safeFetch(url, init);\n await ensureOk(response);\n\n if (!response.body) {\n throw new LLMProviderError('Response body is null (streaming unsupported)', {\n status: response.status,\n });\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n /** tool call 累积器,按 index 聚合 args 字符串 */\n const accumulators = new Map<number, ToolCallAccumulator>();\n let finishReason: LLMStopReason | undefined;\n let usage: LLMUsage | undefined;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // SSE 事件以 \\n\\n 分隔\n let sep: number;\n while ((sep = buffer.indexOf('\\n\\n')) >= 0) {\n const eventStr = buffer.slice(0, sep);\n buffer = buffer.slice(sep + 2);\n\n const data = extractSSEData(eventStr);\n if (data === null) continue; // 注释行 / 心跳行 / 非 data 行\n\n if (data === '[DONE]') {\n // emit 最终 chunk(含累积的 toolCalls + finishReason + usage)\n yield finalizeStreamChunk(accumulators, finishReason, usage);\n return;\n }\n\n let chunk: OpenAIStreamChunkJson;\n try {\n chunk = JSON.parse(data) as OpenAIStreamChunkJson;\n } catch {\n const excerpt = data.slice(0, 500);\n throw new LLMProviderError(`Invalid SSE chunk: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n const delta = chunk.choices?.[0]?.delta;\n if (delta) {\n // 内容增量\n if (typeof delta.content === 'string' && delta.content.length > 0) {\n yield { deltaContent: delta.content };\n }\n // tool_calls 增量累积\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n accumulateToolCall(accumulators, tc);\n }\n }\n }\n\n const fr = chunk.choices?.[0]?.finish_reason;\n if (fr) finishReason = mapStopReason(fr);\n if (chunk.usage) usage = mapUsage(chunk.usage);\n }\n }\n\n // 流自然结束(无 [DONE]),emit 最终 chunk\n yield finalizeStreamChunk(accumulators, finishReason, usage);\n } finally {\n reader.releaseLock();\n }\n }\n\n return { complete, stream };\n}\n\n// ─── 辅助函数 ──────────────────────────────────────\n\n/** LLMMessage → OpenAI 消息格式 */\nfunction toOpenAIMessage(msg: LLMMessage): unknown {\n const out: Record<string, unknown> = {\n role: msg.role,\n content: msg.content,\n };\n if (msg.toolCallId !== undefined) out.tool_call_id = msg.toolCallId;\n if (msg.toolCalls !== undefined && msg.toolCalls.length > 0) {\n out.tool_calls = msg.toolCalls.map((tc) => ({\n id: tc.id,\n type: 'function',\n function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },\n }));\n }\n return out;\n}\n\n/** LLMToolDefinition → OpenAI tool 定义格式 */\nfunction toOpenAITool(tool: LLMToolDefinition): unknown {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.input,\n },\n };\n}\n\n/** OpenAI tool_calls 数组 → LLMToolCall[],JSON.parse 每个 arguments */\nfunction parseToolCalls(\n toolCalls: OpenAIToolCall[] | undefined,\n bodyText: string,\n status: number,\n): LLMToolCall[] | undefined {\n if (!toolCalls || toolCalls.length === 0) return undefined;\n\n const result: LLMToolCall[] = [];\n for (let i = 0; i < toolCalls.length; i++) {\n const tc = toolCalls[i];\n const argsStr = tc?.function?.arguments ?? '{}';\n let args: Record<string, unknown>;\n try {\n args = JSON.parse(argsStr);\n } catch {\n const excerpt = argsStr.slice(0, 500);\n throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {\n status,\n body: bodyText.slice(0, 500),\n });\n }\n result.push({\n id: tc?.id ?? `call_${i}`,\n name: tc?.function?.name ?? '',\n arguments: args,\n });\n }\n return result;\n}\n\n/** OpenAI finish_reason → LLMStopReason */\nfunction mapStopReason(fr: string | undefined | null): LLMStopReason {\n switch (fr) {\n case 'stop':\n return 'stop';\n case 'tool_calls':\n return 'tool_calls';\n case 'length':\n return 'length';\n case 'content_filter':\n return 'content_filter';\n default:\n return 'other';\n }\n}\n\n/** OpenAI usage → LLMUsage */\nfunction mapUsage(u?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}): LLMUsage | undefined {\n if (!u) return undefined;\n return {\n promptTokens: u.prompt_tokens ?? 0,\n completionTokens: u.completion_tokens ?? 0,\n totalTokens: u.total_tokens ?? 0,\n };\n}\n\n/** 从 SSE 事件块提取 data: 行内容(多行用 \\n 拼接) */\nfunction extractSSEData(event: string): string | null {\n const dataLines: string[] = [];\n for (const line of event.split('\\n')) {\n // 跳过空行 / 注释行 / 心跳行\n if (line === '' || line.startsWith(':')) continue;\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n // 其他字段(event: / id: / retry:)忽略,OpenAI 不用\n }\n if (dataLines.length === 0) return null;\n return dataLines.join('\\n');\n}\n\n/** 累积 tool call chunk 到按 index 的 Map */\nfunction accumulateToolCall(\n accumulators: Map<number, ToolCallAccumulator>,\n tc: OpenAIToolCall,\n): void {\n const idx = tc.index ?? 0;\n const acc = accumulators.get(idx) ?? { argsString: '' };\n if (tc.id) acc.id = tc.id;\n if (tc.function?.name) acc.name = tc.function.name;\n if (tc.function?.arguments) acc.argsString += tc.function.arguments;\n accumulators.set(idx, acc);\n}\n\n/** 流结束时把累积器转为 LLMToolCall[] + finishReason + usage 一并 emit */\nfunction finalizeStreamChunk(\n accumulators: Map<number, ToolCallAccumulator>,\n finishReason: LLMStopReason | undefined,\n usage: LLMUsage | undefined,\n): LLMStreamChunk {\n const toolCalls: LLMToolCall[] = [];\n if (accumulators.size > 0) {\n const indices = Array.from(accumulators.keys()).sort((a, b) => a - b);\n for (const idx of indices) {\n const acc = accumulators.get(idx)!;\n // 跳过不完整(无 id 或 name)的累积,异常流不应阻止结束\n if (!acc.id || !acc.name) continue;\n let args: Record<string, unknown>;\n try {\n args = acc.argsString ? JSON.parse(acc.argsString) : {};\n } catch {\n const excerpt = acc.argsString.slice(0, 500);\n throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {\n body: excerpt,\n });\n }\n toolCalls.push({\n id: acc.id,\n name: acc.name,\n arguments: args,\n });\n }\n }\n\n const chunk: LLMStreamChunk = {};\n if (toolCalls.length > 0) chunk.toolCalls = toolCalls;\n if (finishReason) chunk.finishReason = finishReason;\n if (usage) chunk.usage = usage;\n return chunk;\n}\n","import type { LlmConfig } from '@faapi/faapi';\nimport { createOpenAIProvider } from './providers/openai';\n\n/**\n * LLM Provider 抽象层\n *\n * 统一 `complete` / `stream` 接口,屏蔽 OpenAI / Anthropic 等 LLM 服务差异。\n * [reactLoop](./reactLoop.md) 与 [Agent 类](./agent.md) 通过此接口调用 LLM,\n * 与具体 provider 解耦。\n *\n * 详见 [provider.md](./provider.md)。\n */\n\n/**\n * 对话消息\n *\n * 四种 role 与 OpenAI chat completions 一致:\n * - `system` —— 系统提示词(agent 的 systemPrompt)\n * - `user` —— 用户输入\n * - `assistant` —— LLM 回复(可能含 toolCalls)\n * - `tool` —— tool 执行结果(需带 toolCallId 标识对应哪个 tool_call)\n */\nexport interface LLMMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n /** 消息内容(assistant 角色 + tool_calls 时可能为空字符串) */\n content: string;\n /** role='tool' 时:对应的 tool_call ID(用于 LLM 关联 tool 结果) */\n toolCallId?: string;\n /** role='assistant' 时:LLM 请求的 tool 调用(reactLoop 据此执行 tool) */\n toolCalls?: LLMToolCall[];\n}\n\n/**\n * LLM 请求的 tool 调用\n *\n * 由 LLM 在 assistant 消息中返回。`arguments` 已 JSON.parse,\n * reactLoop 直接传给 tool 函数。\n */\nexport interface LLMToolCall {\n /** tool call ID(provider 分配,用于匹配 tool 结果) */\n id: string;\n /** tool 名(匹配 LLMToolDefinition.name) */\n name: string;\n /** tool 参数(已 JSON.parse 的对象) */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool 定义\n *\n * 由 [reactLoop](./reactLoop.md) 从 [toolRegistry](../../faapi/src/injection/toolRegistry.md)\n * + [agentRegistry.resolveSubAgents](../../faapi/src/injection/agentRegistry.md) 组装:\n * - 常规 tool:`input` 来自 AST 提取的 zod schema(JSON Schema 形式)\n * - agent-as-tool:`input` 为自由 schema(agent 参数开放)\n */\nexport interface LLMToolDefinition {\n /** tool 名(如 `weather.getWeather` 或 `agent.researcher`) */\n name: string;\n /** tool 描述(对 LLM 可见,引导 LLM 选择调用) */\n description?: string;\n /** JSON Schema 对象(描述 tool 参数结构) */\n input: Record<string, unknown>;\n}\n\n/**\n * complete / stream 的入参\n *\n * `model` / `temperature` / `maxTokens` 优先级高于 [LlmConfig](../../faapi/src/config/configTypes.md)\n * (agent 自身 `config.model` 覆盖全局默认)。\n */\nexport interface LLMCompleteRequest {\n /** 对话消息(含历史 + 当前轮) */\n messages: LLMMessage[];\n /** 可用 tool 列表(未提供时 LLM 不会发起 tool_call) */\n tools?: LLMToolDefinition[];\n /** 覆盖 LlmConfig.model(agent 自身配置优先) */\n model?: string;\n /** 采样温度(0~2) */\n temperature?: number;\n /** 最大生成 token 数 */\n maxTokens?: number;\n}\n\n/**\n * LLM 响应的停止原因\n *\n * - `stop` —— LLM 主动结束(自然结束 / 遇到 stop 序列)\n * - `tool_calls` —— LLM 请求调用 tool(reactLoop 据此进入下一轮)\n * - `length` —— 达到 max_tokens 上限\n * - `content_filter` —— 内容过滤触发\n * - `other` —— 其他原因(未识别的 finish_reason)\n */\nexport type LLMStopReason = 'stop' | 'tool_calls' | 'length' | 'content_filter' | 'other';\n\n/**\n * complete 的返回\n *\n * `message.toolCalls` 不为空时 stopReason 应为 `'tool_calls'`。\n */\nexport interface LLMResponse {\n /** assistant 消息(含 content + 可选 toolCalls) */\n message: LLMMessage;\n /** 停止原因(reactLoop 据此判断是否进入下一轮) */\n stopReason: LLMStopReason;\n /** token 用量(部分 provider 不返回) */\n usage?: LLMUsage;\n}\n\n/**\n * Token 用量\n */\nexport interface LLMUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n}\n\n/**\n * stream 的单个 chunk\n *\n * - 内容流:`deltaContent` 为增量 token\n * - tool 调用:累积完成后在最终 chunk 一并 emit `toolCalls`\n * - 结束:最终 chunk 含 `finishReason` + 可选 `usage`\n */\nexport interface LLMStreamChunk {\n /** 增量内容(streaming token) */\n deltaContent?: string;\n /** 累积完成的 tool 调用(在最终 chunk 出现) */\n toolCalls?: LLMToolCall[];\n /** 结束原因(只在最终 chunk 出现) */\n finishReason?: LLMStopReason;\n /** token 用量(部分 provider 在最终 chunk 提供) */\n usage?: LLMUsage;\n}\n\n/**\n * LLM Provider 抽象接口\n *\n * 实现方:\n * - [createOpenAIProvider](./providers/openai.md) —— OpenAI 兼容 API\n *\n * 消费方:\n * - [reactLoop](./reactLoop.md) Phase 3.3 —— 调 complete / stream 执行 ReAct 循环\n * - [Agent 类](./agent.md) Phase 3.4 —— 构造时持有 provider 实例\n */\nexport interface LLMProvider {\n /** 非流式:阻塞到 LLM 返回完整响应 */\n complete(request: LLMCompleteRequest): Promise<LLMResponse>;\n /** 流式:异步迭代 chunk(增量 token + tool call + 结束) */\n stream(request: LLMCompleteRequest): AsyncIterable<LLMStreamChunk>;\n}\n\n/**\n * LLM Provider 错误\n *\n * 由具体 provider 抛出,包含 HTTP 状态码(网络错误为 `undefined`)和响应体摘要。\n * 业务方可通过 `instanceof LLMProviderError` 区分 LLM 错误与其他错误。\n *\n * 重新从 [./providers/openai](./providers/openai.md) 导出,便于业务方从此模块统一捕获。\n */\nexport { LLMProviderError } from './providers/openai';\n\n/**\n * 按 `config.provider` 路由到对应的 LLM 适配器\n *\n * Phase 3.2 仅支持 `'openai'`,其他值抛错不静默降级(参考 AGENTS.md §6.3)。\n *\n * @param config LLM 提供方配置(来自 faapi.config.ts 的 `agent.llm`)\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 {\n LLMMessage,\n LLMProvider,\n LLMStopReason,\n LLMToolCall,\n LLMToolDefinition,\n LLMUsage,\n} from './provider';\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 */\nexport type ToolExecutor = (name: string, args: Record<string, unknown>) => Promise<unknown>;\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\n/**\n * 非流式循环结果\n */\nexport interface ReactLoopResult {\n /** 最终 assistant 消息内容 */\n content: string;\n /** 完整对话历史(system + user + assistant + tool 消息) */\n messages: LLMMessage[];\n /** 使用的轮数(含最终轮) */\n turns: number;\n /** 最终轮的停止原因 */\n stopReason: LLMStopReason;\n /** 累计 token 用量(多轮累加,provider 不返回时为 `undefined`) */\n usage?: LLMUsage;\n}\n\n/**\n * 流式循环的单个 chunk\n *\n * 每个 chunk 至多含一个字段:\n * - `deltaContent` — LLM 增量 token(多次 yield)\n * - `toolCall` — tool 开始执行\n * - `toolResult` — tool 执行完成\n * - `done` — 循环结束(只 yield 一次)\n */\nexport interface ReactLoopStreamChunk {\n /** LLM 增量 token */\n deltaContent?: string;\n /** tool 开始执行(LLM 请求调用 tool) */\n toolCall?: { name: string; arguments: Record<string, unknown> };\n /** tool 执行完成(含结果) */\n toolResult?: { name: string; result: string };\n /** 循环结束 */\n done?: {\n content: string;\n turns: number;\n stopReason: LLMStopReason;\n usage?: LLMUsage;\n };\n}\n\n/**\n * reactLoop 系统级错误\n *\n * 目前仅用于 `maxTurns` 超限。tool 执行错误不抛此类型——它们被 catch 后回传 LLM。\n */\nexport class ReactLoopError extends Error {\n /** 配置的 maxTurns 值 */\n readonly maxTurns: number;\n\n constructor(message: string, maxTurns: number) {\n super(message);\n this.name = 'ReactLoopError';\n this.maxTurns = maxTurns;\n }\n}\n\n/** 默认最大轮数 */\nconst DEFAULT_MAX_TURNS = 10;\n\n/** 把 tool 执行结果转为字符串(非 string 自动 JSON.stringify) */\nfunction stringifyResult(result: unknown): string {\n if (typeof result === 'string') return result;\n if (result === undefined) return '';\n return JSON.stringify(result);\n}\n\n/** 把 tool 执行错误转为字符串 */\nfunction stringifyError(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\n/** 累加 usage */\nfunction accumulateUsage(a: LLMUsage | undefined, b: LLMUsage): LLMUsage {\n if (!a) return { ...b };\n return {\n promptTokens: a.promptTokens + b.promptTokens,\n completionTokens: a.completionTokens + b.completionTokens,\n totalTokens: a.totalTokens + b.totalTokens,\n };\n}\n\n/** 构造初始 messages(可选 system + user input) */\nfunction buildInitialMessages(input: string, systemPrompt?: string): LLMMessage[] {\n const messages: LLMMessage[] = [];\n if (systemPrompt) {\n messages.push({ role: 'system', content: systemPrompt });\n }\n messages.push({ role: 'user', content: input });\n return messages;\n}\n\n/** 构造 LLM complete/stream 请求参数(除 messages 外的公共字段) */\nfunction buildRequestExtras(config: ReactLoopConfig) {\n return {\n tools: config.tools,\n model: config.model,\n temperature: config.temperature,\n maxTokens: config.maxTokens,\n };\n}\n\n// ─── reactLoop(非流式)──────────────────────────────\n\n/**\n * 执行 ReAct 循环(非流式)\n *\n * 反复调 `provider.complete()` → 执行 tool → 回传结果,直到 LLM 返回 `stop`(或其他非 `tool_calls` 原因)或超出 `maxTurns`。\n *\n * @param input 用户输入\n * @param config 循环配置\n * @returns 最终结果(content + messages + turns + stopReason + usage)\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.complete 抛错时立即传播\n */\nexport async function reactLoop(input: string, config: ReactLoopConfig): Promise<ReactLoopResult> {\n const messages = buildInitialMessages(input, config.systemPrompt);\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n const extras = buildRequestExtras(config);\n let totalUsage: LLMUsage | undefined;\n let turns = 0;\n\n while (turns < maxTurns) {\n turns++;\n\n const response = await config.provider.complete({\n messages: [...messages],\n ...extras,\n });\n\n if (response.usage) {\n totalUsage = accumulateUsage(totalUsage, response.usage);\n }\n\n // 把 assistant 消息加入历史\n messages.push(response.message);\n\n // 非 tool_calls → 循环结束\n if (response.stopReason !== 'tool_calls' || !response.message.toolCalls) {\n return {\n content: response.message.content,\n messages,\n turns,\n stopReason: response.stopReason,\n usage: totalUsage,\n };\n }\n\n // 执行每个 tool call\n for (const toolCall of response.message.toolCalls) {\n let resultStr: string;\n try {\n const result = await config.executeTool(toolCall.name, toolCall.arguments);\n resultStr = stringifyResult(result);\n } catch (err) {\n resultStr = stringifyError(err);\n }\n messages.push({\n role: 'tool',\n content: resultStr,\n toolCallId: toolCall.id,\n });\n }\n }\n\n throw new ReactLoopError(\n `Max turns (${maxTurns}) exceeded — agent did not converge to a final answer`,\n maxTurns,\n );\n}\n\n// ─── reactLoopStream(流式)──────────────────────────\n\n/**\n * 执行 ReAct 循环(流式)\n *\n * 使用 `provider.stream()` 异步迭代 chunks,yield `deltaContent` + `toolCall` + `toolResult` + `done`。\n *\n * @param input 用户输入\n * @param config 循环配置\n * @yields {ReactLoopStreamChunk} 流式 chunk\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.stream 抛错时立即传播\n */\nexport async function* reactLoopStream(\n input: string,\n config: ReactLoopConfig,\n): AsyncIterable<ReactLoopStreamChunk> {\n const messages = buildInitialMessages(input, config.systemPrompt);\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n const extras = buildRequestExtras(config);\n let totalUsage: LLMUsage | undefined;\n let turns = 0;\n\n while (turns < maxTurns) {\n turns++;\n\n let turnContent = '';\n let toolCalls: LLMToolCall[] | undefined;\n let finishReason: LLMStopReason | undefined;\n\n for await (const chunk of config.provider.stream({\n messages: [...messages],\n ...extras,\n })) {\n // 增量内容\n if (typeof chunk.deltaContent === 'string' && chunk.deltaContent.length > 0) {\n turnContent += chunk.deltaContent;\n yield { deltaContent: chunk.deltaContent };\n }\n\n // tool_calls(在最终 chunk 出现,含 id/name/arguments)\n if (chunk.toolCalls && chunk.toolCalls.length > 0) {\n toolCalls = chunk.toolCalls;\n }\n\n // finishReason + usage(在最终 chunk 出现)\n if (chunk.finishReason) {\n finishReason = chunk.finishReason;\n }\n if (chunk.usage) {\n totalUsage = accumulateUsage(totalUsage, chunk.usage);\n }\n }\n\n // 把 assistant 消息加入历史(含 toolCalls,供下一轮 LLM 上下文)\n const assistantMessage: LLMMessage = {\n role: 'assistant',\n content: turnContent,\n };\n if (toolCalls) {\n assistantMessage.toolCalls = toolCalls;\n }\n messages.push(assistantMessage);\n\n // 非 tool_calls → 循环结束\n if (finishReason !== 'tool_calls' || !toolCalls) {\n yield {\n done: {\n content: turnContent,\n turns,\n stopReason: finishReason ?? 'other',\n usage: totalUsage,\n },\n };\n return;\n }\n\n // 执行每个 tool call\n for (const toolCall of toolCalls) {\n yield { toolCall: { name: toolCall.name, arguments: toolCall.arguments } };\n\n let resultStr: string;\n try {\n const result = await config.executeTool(toolCall.name, toolCall.arguments);\n resultStr = stringifyResult(result);\n } catch (err) {\n resultStr = stringifyError(err);\n }\n\n yield { toolResult: { name: toolCall.name, result: resultStr } };\n\n messages.push({\n role: 'tool',\n content: resultStr,\n toolCallId: toolCall.id,\n });\n }\n }\n\n throw new ReactLoopError(\n `Max turns (${maxTurns}) exceeded — agent did not converge to a final answer`,\n maxTurns,\n );\n}\n","import type {\n AgentMetadata,\n AgentModule,\n AgentToolDescriptor,\n ToolMetadata,\n ToolModule,\n} from '@faapi/faapi';\nimport type { LLMProvider, LLMToolDefinition } from './provider';\nimport {\n reactLoop,\n reactLoopStream,\n type ReactLoopConfig,\n type ReactLoopResult,\n type ReactLoopStreamChunk,\n} from './reactLoop';\n\n/**\n * Agent 类——按 `agent.name` 查找元数据、组装 tool 列表、提供 `run` / `stream` / `asTool`\n *\n * 把 [reactLoop](./reactLoop.md) 与 faapi 核心的 agent/tool 注册表粘合起来:\n * - **组装 tool 列表**——合并 `resolveAgentTools`(agent 显式声明的 `tools`)+ `defaultTools` + sub-agent\n * - **执行 tool**——`reactLoop` 调 `executeTool(name, args)` 时,Agent 路由:\n * - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用\n * - `agent.` 前缀 → 递归构造 sub-agent 调用(含 `maxAgentDepth` 防护)\n * - **递归防护**——`maxAgentDepth` 限制 agent 调用 agent 的深度\n * - **自定义 run**——agent handler 导出 `run` 函数时,sub-agent 走自定义逻辑\n *\n * 详见 [agent.md](./agent.md)。\n */\n\n/** 默认最大 agent 递归深度(根 agent depth=1,sub-agent 递增) */\nconst DEFAULT_MAX_AGENT_DEPTH = 3;\n\n/**\n * 全局 agent 配置覆盖\n *\n * 来自 faapi.config.ts 的 `agent` 块,提供全局默认值。\n * agent 自身 `config.maxTurns` / `config.model` 优先于全局配置。\n */\nexport interface AgentRuntimeConfig {\n /** 默认最大对话轮数(agent 自身 maxTurns 优先) */\n maxTurns?: number;\n /** agent 调用 agent 的最大递归深度(默认 3) */\n maxAgentDepth?: number;\n /** 默认 tool 列表,所有 agent 都可用 */\n defaultTools?: string[];\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 实例 */\n provider: LLMProvider;\n /** 当前 agent 名 */\n agentName: string;\n /** 项目根目录(Phase 3.5 接线时用于加载器) */\n rootDir: string;\n /** 全局 agent 配置覆盖 */\n config?: AgentRuntimeConfig;\n /** 查 agent 元数据(对应 agentRegistry.getAgent) */\n getAgent: (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) */\n resolveSubAgents: (name: string) => AgentMetadata[];\n /** 动态 import tool handler(对应 loadToolModule) */\n loadToolModule: (filePath: string, functionName: string) => Promise<ToolModule>;\n /** 动态 import agent handler(对应 loadAgentModule) */\n loadAgentModule: (filePath: string, hasConfig: boolean, 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 运行时依赖(访问器 + provider + 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 * @param input 用户输入\n * @returns 最终结果(content + messages + turns + stopReason + usage)\n * @throws {AgentError} agent 未注册\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.complete 抛错时立即传播\n */\n async run(input: string): Promise<ReactLoopResult> {\n const config = await this.buildLoopConfig();\n return reactLoop(input, config);\n }\n\n /**\n * 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)\n *\n * @param input 用户输入\n * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)\n * @throws {AgentError} agent 未注册\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.stream 抛错时立即传播\n */\n async *stream(input: string): AsyncIterable<ReactLoopStreamChunk> {\n const config = await this.buildLoopConfig();\n yield* reactLoopStream(input, config);\n }\n\n /**\n * 把自身包装为 `AgentToolDescriptor` 供 LLM 当 tool 调用\n *\n * 与 [agentRegistry.asTool](../../faapi/src/injection/agentRegistry.md) 同构——\n * Agent 类自带此方法便于在注入器场景直接调用(不必再过注册表)。\n *\n * @returns `AgentToolDescriptor` 或 `undefined`(agent 未注册)\n */\n asTool(): AgentToolDescriptor | undefined {\n const meta = this.deps.getAgent(this.deps.agentName);\n if (!meta) return undefined;\n return {\n kind: 'agent',\n name: `agent.${meta.name}`,\n agentName: meta.name,\n description: meta.description,\n metadata: meta,\n };\n }\n\n // ─── 内部方法 ────────────────────────────────────────\n\n /**\n * 查询 tool schema(带缓存)\n *\n * `buildToolDefinitions` 与 `executeTool` 共用此方法——\n * 首次调用触发 `deps.resolveToolSchema`(加载 zod.js + 生成 JSON Schema),\n * 后续命中缓存直接返回(含 `undefined` 结果,用 `has` 区分未解析 vs 解析为空)。\n *\n * `deps.resolveToolSchema` 未提供时直接返回 `undefined`,不写缓存。\n */\n private async getToolSchema(tool: ToolMetadata): Promise<ToolSchemaResolution | undefined> {\n if (!this.deps.resolveToolSchema) return undefined;\n if (this.schemaCache.has(tool.name)) {\n return this.schemaCache.get(tool.name);\n }\n const resolved = await this.deps.resolveToolSchema(tool);\n this.schemaCache.set(tool.name, resolved);\n return resolved;\n }\n\n /**\n * 组装 ReactLoopConfig\n *\n * 1. 查 agent 元数据(未注册抛 AgentError)\n * 2. buildToolDefinitions 组装 tool 列表\n * 3. config 字段优先级:agent 元数据 > 全局 AgentRuntimeConfig\n */\n private async buildLoopConfig(): Promise<ReactLoopConfig> {\n const meta = this.deps.getAgent(this.deps.agentName);\n if (!meta) {\n throw new AgentError(`Agent \"${this.deps.agentName}\" is not registered`);\n }\n\n const tools = await this.buildToolDefinitions();\n\n return {\n provider: this.deps.provider,\n systemPrompt: meta.systemPrompt,\n model: meta.model,\n maxTurns: meta.maxTurns ?? this.deps.config?.maxTurns,\n tools,\n executeTool: async (name, args) => this.executeTool(name, args),\n };\n }\n\n /**\n * 组装 LLM 可见 tool 列表\n *\n * 合并三个来源(按 `name` 去重,先入者保留):\n * 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用\n * 2. **全局 defaultTools** —— `config.defaultTools` 中的 tool 名(所有 agent 共享)\n * 3. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`\n *\n * 每个常规 tool 的 `input`:\n * - `resolveToolSchema` 提供 → 用其 `jsonSchema`\n * - 未提供 / tool 无 `inputTypeName` → 自由 schema `{ type: 'object' }`\n *\n * sub-agent 的 `input` 始终为 `{ type: 'object' }`(agent 参数开放)。\n */\n private async buildToolDefinitions(): Promise<LLMToolDefinition[]> {\n const definitions = new Map<string, LLMToolDefinition>();\n\n // 1. resolveAgentTools(agent 显式声明的 tools 引用)\n for (const tool of this.deps.resolveAgentTools(this.deps.agentName)) {\n if (definitions.has(tool.name)) continue;\n const schemaRes = await this.getToolSchema(tool);\n definitions.set(tool.name, {\n name: tool.name,\n description: tool.description,\n input: schemaRes?.jsonSchema ?? { type: 'object' },\n });\n }\n\n // 2. 全局 defaultTools(所有 agent 共享,去重)\n const defaultTools = this.deps.config?.defaultTools;\n if (defaultTools) {\n for (const toolName of defaultTools) {\n if (definitions.has(toolName)) continue; // 去重\n const tool = this.deps.getTool(toolName);\n if (!tool) continue; // 未找到的 tool 名静默跳过\n const schemaRes = await this.getToolSchema(tool);\n definitions.set(tool.name, {\n name: tool.name,\n description: tool.description,\n input: schemaRes?.jsonSchema ?? { type: 'object' },\n });\n }\n }\n\n // 3. sub-agent(包装为 agent.<name>)\n for (const subAgent of this.deps.resolveSubAgents(this.deps.agentName)) {\n const name = `agent.${subAgent.name}`;\n if (definitions.has(name)) continue;\n definitions.set(name, {\n name,\n description: subAgent.description,\n input: { type: 'object' },\n });\n }\n\n return Array.from(definitions.values());\n }\n\n /**\n * tool 执行路由(由 reactLoop 调用)\n *\n * - `agent.` 前缀 → {@link executeSubAgent} 递归\n * - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用\n *\n * **常规 tool 校验失败**:不抛错,返回 `{ error }` 对象——reactLoop stringify 后\n * 作为 tool 结果回传 LLM,LLM 可据此修正参数重试。\n *\n * **tool 未找到 / 加载失败**:抛错,被 reactLoop catch 后同样回传 LLM。\n */\n private async executeTool(name: string, args: Record<string, unknown>): Promise<unknown> {\n // sub-agent 递归\n if (name.startsWith('agent.')) {\n return this.executeSubAgent(name.slice(6), args);\n }\n\n // 常规 tool\n const tool = this.deps.getTool(name);\n if (!tool) {\n throw new Error(`Tool \"${name}\" not found`);\n }\n\n // 可选 input 校验\n const schemaRes = await this.getToolSchema(tool);\n let callArgs = args;\n if (schemaRes) {\n const result = schemaRes.validate(args);\n if (!result.ok) {\n // 校验失败:返回 { error } 对象,不调用 handler——错误回传 LLM 重试\n return { error: result.error };\n }\n callArgs = result.value ?? args;\n }\n\n const mod = await this.deps.loadToolModule(tool.filePath, tool.functionName);\n return await mod.handler(callArgs);\n }\n\n /**\n * sub-agent 递归执行\n *\n * 1. `maxAgentDepth` 防护——超限抛 {@link AgentRecursionError}\n * 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`\n * 3. 无 `run` 时调 `subAgent.run(JSON.stringify(args))` 走默认 reactLoop\n *\n * 自定义 run 接收原始 args 对象;默认 reactLoop 接收 stringify 后的 args\n * 作为 user 消息(agent-as-tool input 为开放式 JSON)。\n */\n private async executeSubAgent(subName: string, args: Record<string, unknown>): Promise<unknown> {\n const newDepth = this.depth + 1;\n const maxDepth = this.deps.config?.maxAgentDepth ?? DEFAULT_MAX_AGENT_DEPTH;\n if (newDepth > maxDepth) {\n throw new AgentRecursionError(maxDepth, newDepth);\n }\n\n // 构造子 agent(复用父 deps,仅覆盖 agentName)\n const subDeps: AgentDeps = { ...this.deps, agentName: subName };\n const subAgent = new Agent(subDeps, newDepth);\n\n // 自定义 run:sub-agent handler 导出 run 函数时走自定义逻辑\n const meta = this.deps.getAgent(subName);\n if (meta?.hasRun) {\n const mod = await this.deps.loadAgentModule(meta.filePath, meta.hasConfig, meta.hasRun);\n if (mod.run) {\n return await mod.run(args);\n }\n }\n\n // 默认 reactLoop:stringify args 作为 user 消息\n const result = await subAgent.run(typeof args === 'string' ? args : JSON.stringify(args));\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 * llm: { provider: 'openai', apiKey: process.env.OPENAI_API_KEY, model: 'gpt-4o' },\n * defaultAgent: 'researcher',\n * maxTurns: 10,\n * },\n * plugins: ['@faapi/agent'],\n * } satisfies FaapiConfig;\n * ```\n *\n * 插件 setup 时:\n * 1. 读 `config.agent.llm` → `createProvider` → LLMProvider 实例(单例)\n * 2. 读 `config.agent.defaultAgent` / `maxTurns` / `maxAgentDepth` / `defaultTools`\n * 3. 从 `@faapi/faapi` import 注册表/加载器访问器(getAgent / getTool / resolveAgentTools /\n * resolveSubAgents / loadAgentModule / loadToolModule)\n * 4. `registerAgentHandleFactory` 注册工厂——每次请求时构造 [Agent](./agent.md) 实例注入到\n * handler 的 `agent` 参数\n *\n * 配置缺失时(`agent.llm` 或 `agent.defaultAgent` 未设置)跳过工厂注册并打印警告,\n * handler 的 `agent` 参数注入 `undefined`。\n *\n * 详见 [plugin.md](./plugin.md)。\n */\n\nimport {\n registerAgentHandleFactory,\n getAgent,\n getTool,\n resolveAgentTools,\n resolveSubAgents,\n loadAgentModule,\n loadToolModule,\n loadToolSchema,\n type FaapiPlugin,\n type PluginContext,\n type AgentConfig,\n type ToolMetadata,\n} from '@faapi/faapi';\nimport { z } from 'zod';\nimport { Agent, type AgentRuntimeConfig, type ToolSchemaResolution } from './agent';\nimport { createProvider } from './provider';\n\n/**\n * 加载 tool 的 zod.js → 生成 JSON Schema + 校验函数\n *\n * 模块级函数(非 setup 内闭包)——setup 时用 `rootDir` 偏函数绑定一次,\n * 工厂内直接复用,避免每次请求重建闭包。\n *\n * 详见 [plugin.md](./plugin.md) 的 resolveToolSchema 实现。\n *\n * @param tool tool 元数据(含 filePath / inputTypeName)\n * @param rootDir 项目根目录(用于 dev 按需编译模式)\n * @returns `ToolSchemaResolution` 或 `undefined`(zod.js 不存在 / tool 无 inputTypeName)\n */\nasync function resolveToolSchemaImpl(\n tool: ToolMetadata,\n rootDir: string,\n): Promise<ToolSchemaResolution | undefined> {\n const schemaMod = await loadToolSchema(tool, rootDir);\n if (!schemaMod) return undefined;\n const schema = schemaMod.schema as z.ZodType;\n return {\n jsonSchema: z.toJSONSchema(schema),\n validate: (input) => {\n const result = schema.safeParse(input);\n if (result.success) {\n return { ok: true as const, value: result.data as Record<string, unknown> };\n }\n return { ok: false as const, error: result.error.message };\n },\n } satisfies ToolSchemaResolution;\n}\n\n/**\n * 从 PluginContext.config 读取 agent 配置\n *\n * `agent` 不是 FAAPI_CONFIG_KEYS 的成员(它是 agent 子系统配置,非核心配置),\n * 因此会随自定义业务配置一起传到 `ctx.config`。\n */\nfunction readAgentConfig(ctx: PluginContext): AgentConfig | undefined {\n const raw = ctx.config?.agent;\n if (raw === undefined || raw === null) return undefined;\n return raw as AgentConfig;\n}\n\n/**\n * @faapi/agent faapi 插件入口\n *\n * 在 faapi.config.ts 的 `plugins` 字段中声明 `'@faapi/agent'` 即可启用。\n * 插件加载后,handler 的 `agent` 参数可注入可用的 [AgentHandle](./agentHandle.md)。\n */\nconst agentPlugin: FaapiPlugin = {\n name: '@faapi/agent',\n setup(ctx: PluginContext): void {\n const agentConfig = readAgentConfig(ctx);\n\n // 必要配置检查——缺失时跳过工厂注册,agent 参数注入 undefined\n if (!agentConfig?.llm) {\n console.warn(\n '! @faapi/agent: config.agent.llm not configured, agent parameter injection disabled',\n );\n return;\n }\n if (!agentConfig.defaultAgent) {\n console.warn(\n '! @faapi/agent: config.agent.defaultAgent not configured, agent parameter injection disabled',\n );\n return;\n }\n\n // 创建 LLM provider(单例,所有请求共享)\n const provider = createProvider(agentConfig.llm);\n\n // 全局 agent 运行时配置覆盖\n const runtimeConfig: AgentRuntimeConfig = {\n maxTurns: agentConfig.maxTurns,\n maxAgentDepth: agentConfig.maxAgentDepth,\n defaultTools: agentConfig.defaultTools,\n };\n\n const rootDir = ctx.rootDir;\n const defaultAgent = agentConfig.defaultAgent;\n // resolveToolSchema 偏函数(setup 内创建一次,工厂内复用,避免每次请求重建闭包)\n const resolveToolSchema = (tool: ToolMetadata) => resolveToolSchemaImpl(tool, rootDir);\n\n // 注册 agent handle 工厂——每次请求时构造 Agent 实例\n // Agent 构造轻量(仅存 deps),实际 LLM 调用在 run/stream 时才发生\n registerAgentHandleFactory(() => {\n return new Agent({\n provider,\n agentName: defaultAgent,\n rootDir,\n config: runtimeConfig,\n // 注册表/加载器访问器——从 @faapi/faapi import 的单例模块\n // createAppBase 启动时已水合 agentRegistry / toolRegistry\n getAgent,\n getTool,\n resolveAgentTools,\n resolveSubAgents,\n // 加载器包装:注入 rootDir 用于 dev 按需编译模式\n loadToolModule: (filePath, functionName) => loadToolModule(filePath, functionName, rootDir),\n loadAgentModule: (filePath, hasConfig, hasRun) =>\n loadAgentModule(filePath, hasConfig, hasRun, rootDir),\n // tool schema 解析(zod.js → JSON Schema + safeParse 校验)\n resolveToolSchema,\n });\n });\n\n console.log(\n `- @faapi/agent: default agent \"${defaultAgent}\" available via agent parameter injection`,\n );\n },\n};\n\nexport default agentPlugin;\n"],"mappings":";AAuBA,IAAM,mBAAmB;AAGzB,IAAM,uBAAuB,oBAAI,IAAI,CAAC,YAAY,UAAU,SAAS,SAAS,CAAC;AAQxE,IAAM,mBAAN,cAA+B,MAAM;AAAA;AAAA,EAEjC;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,SAAiB,SAA+D;AAC1F,UAAM,SAAS,SAAS,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,MAAS;AAClF,SAAK,OAAO;AACZ,SAAK,SAAS,SAAS;AACvB,SAAK,OAAO,SAAS;AAAA,EACvB;AACF;AAmEO,SAAS,qBAAqB,QAAgC;AACnE,QAAM,WAAW,OAAO,WAAW,kBAAkB,QAAQ,OAAO,EAAE;AACtE,QAAM,SAAS,OAAO;AAGtB,WAAS,iBAAiB,SAAgD;AACxE,UAAM,OAA0B;AAAA,MAC9B,OAAO,QAAQ,SAAS,OAAO;AAAA,MAC/B,UAAU,QAAQ,SAAS,IAAI,eAAe;AAAA,IAChD;AAEA,QAAI,QAAQ,SAAS,QAAQ,MAAM,SAAS,GAAG;AAC7C,WAAK,QAAQ,QAAQ,MAAM,IAAI,YAAY;AAAA,IAC7C;AAEA,QAAI,QAAQ,gBAAgB,OAAW,MAAK,cAAc,QAAQ;AAClE,QAAI,QAAQ,cAAc,OAAW,MAAK,aAAa,QAAQ;AAG/D,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,UAAI,qBAAqB,IAAI,GAAG,EAAG;AACnC,YAAM,QAAS,OAAmC,GAAG;AACrD,UAAI,UAAU,UAAa,EAAE,OAAO,OAAO;AACzC,aAAK,GAAG,IAAI;AAAA,MACd;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAGA,WAAS,eAAuC;AAC9C,UAAM,UAAkC,EAAE,gBAAgB,mBAAmB;AAC7E,QAAI,OAAQ,SAAQ,gBAAgB,UAAU,MAAM;AACpD,WAAO;AAAA,EACT;AAGA,iBAAe,UAAU,KAAa,MAAsC;AAC1E,QAAI;AACF,aAAO,MAAM,MAAM,KAAK,IAAI;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,YAAM,IAAI,iBAAiB,kBAAkB,MAAM,IAAI,EAAE,OAAO,IAAI,CAAC;AAAA,IACvE;AAAA,EACF;AAGA,iBAAe,SAAS,UAAqC;AAC3D,QAAI,SAAS,GAAI,QAAO;AACxB,UAAM,WAAW,MAAM,SAAS,KAAK;AACrC,UAAM,UAAU,SAAS,MAAM,GAAG,GAAG;AACrC,UAAM,IAAI,iBAAiB,QAAQ,SAAS,MAAM,KAAK,OAAO,IAAI;AAAA,MAChE,QAAQ,SAAS;AAAA,MACjB,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAIA,iBAAe,SAAS,SAAmD;AACzE,UAAM,MAAM,GAAG,OAAO;AACtB,UAAM,OAAO,iBAAiB,OAAO;AACrC,UAAM,OAAoB;AAAA,MACxB,QAAQ;AAAA,MACR,SAAS,aAAa;AAAA,MACtB,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B;AAEA,UAAM,WAAW,MAAM,UAAU,KAAK,IAAI;AAC1C,UAAM,SAAS,QAAQ;AAEvB,UAAM,WAAW,MAAM,SAAS,KAAK;AACrC,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,MAAM,QAAQ;AAAA,IAC5B,QAAQ;AACN,YAAM,UAAU,SAAS,MAAM,GAAG,GAAG;AACrC,YAAM,IAAI,iBAAiB,0BAA0B,OAAO,IAAI;AAAA,QAC9D,QAAQ,SAAS;AAAA,QACjB,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,UAAM,SAAS,KAAK,UAAU,CAAC;AAC/B,UAAM,MAAM,QAAQ;AACpB,QAAI,CAAC,UAAU,CAAC,KAAK;AACnB,YAAM,UAAU,SAAS,MAAM,GAAG,GAAG;AACrC,YAAM,IAAI,iBAAiB,6BAA6B;AAAA,QACtD,QAAQ,SAAS;AAAA,QACjB,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,UAAM,UAAU,IAAI,WAAW;AAC/B,UAAM,YAAY,eAAe,IAAI,YAAY,UAAU,SAAS,MAAM;AAE1E,WAAO;AAAA,MACL,SAAS;AAAA,QACP,MAAM;AAAA,QACN;AAAA,QACA;AAAA,MACF;AAAA,MACA,YAAY,cAAc,OAAO,iBAAiB,MAAS;AAAA,MAC3D,OAAO,SAAS,KAAK,KAAK;AAAA,IAC5B;AAAA,EACF;AAIA,kBAAgB,OAAO,SAA4D;AACjF,UAAM,MAAM,GAAG,OAAO;AACtB,UAAM,OAAO,iBAAiB,OAAO;AACrC,SAAK,SAAS;AAEd,UAAM,OAAoB;AAAA,MACxB,QAAQ;AAAA,MACR,SAAS,aAAa;AAAA,MACtB,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B;AAEA,UAAM,WAAW,MAAM,UAAU,KAAK,IAAI;AAC1C,UAAM,SAAS,QAAQ;AAEvB,QAAI,CAAC,SAAS,MAAM;AAClB,YAAM,IAAI,iBAAiB,iDAAiD;AAAA,QAC1E,QAAQ,SAAS;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,UAAM,SAAS,SAAS,KAAK,UAAU;AACvC,UAAM,UAAU,IAAI,YAAY;AAChC,QAAI,SAAS;AAGb,UAAM,eAAe,oBAAI,IAAiC;AAC1D,QAAI;AACJ,QAAI;AAEJ,QAAI;AACF,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AACV,kBAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAGhD,YAAI;AACJ,gBAAQ,MAAM,OAAO,QAAQ,MAAM,MAAM,GAAG;AAC1C,gBAAM,WAAW,OAAO,MAAM,GAAG,GAAG;AACpC,mBAAS,OAAO,MAAM,MAAM,CAAC;AAE7B,gBAAM,OAAO,eAAe,QAAQ;AACpC,cAAI,SAAS,KAAM;AAEnB,cAAI,SAAS,UAAU;AAErB,kBAAM,oBAAoB,cAAc,cAAc,KAAK;AAC3D;AAAA,UACF;AAEA,cAAI;AACJ,cAAI;AACF,oBAAQ,KAAK,MAAM,IAAI;AAAA,UACzB,QAAQ;AACN,kBAAM,UAAU,KAAK,MAAM,GAAG,GAAG;AACjC,kBAAM,IAAI,iBAAiB,sBAAsB,OAAO,IAAI;AAAA,cAC1D,QAAQ,SAAS;AAAA,cACjB,MAAM;AAAA,YACR,CAAC;AAAA,UACH;AAEA,gBAAM,QAAQ,MAAM,UAAU,CAAC,GAAG;AAClC,cAAI,OAAO;AAET,gBAAI,OAAO,MAAM,YAAY,YAAY,MAAM,QAAQ,SAAS,GAAG;AACjE,oBAAM,EAAE,cAAc,MAAM,QAAQ;AAAA,YACtC;AAEA,gBAAI,MAAM,YAAY;AACpB,yBAAW,MAAM,MAAM,YAAY;AACjC,mCAAmB,cAAc,EAAE;AAAA,cACrC;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,KAAK,MAAM,UAAU,CAAC,GAAG;AAC/B,cAAI,GAAI,gBAAe,cAAc,EAAE;AACvC,cAAI,MAAM,MAAO,SAAQ,SAAS,MAAM,KAAK;AAAA,QAC/C;AAAA,MACF;AAGA,YAAM,oBAAoB,cAAc,cAAc,KAAK;AAAA,IAC7D,UAAE;AACA,aAAO,YAAY;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,OAAO;AAC5B;AAKA,SAAS,gBAAgB,KAA0B;AACjD,QAAM,MAA+B;AAAA,IACnC,MAAM,IAAI;AAAA,IACV,SAAS,IAAI;AAAA,EACf;AACA,MAAI,IAAI,eAAe,OAAW,KAAI,eAAe,IAAI;AACzD,MAAI,IAAI,cAAc,UAAa,IAAI,UAAU,SAAS,GAAG;AAC3D,QAAI,aAAa,IAAI,UAAU,IAAI,CAAC,QAAQ;AAAA,MAC1C,IAAI,GAAG;AAAA,MACP,MAAM;AAAA,MACN,UAAU,EAAE,MAAM,GAAG,MAAM,WAAW,KAAK,UAAU,GAAG,SAAS,EAAE;AAAA,IACrE,EAAE;AAAA,EACJ;AACA,SAAO;AACT;AAGA,SAAS,aAAa,MAAkC;AACtD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,MACR,MAAM,KAAK;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK;AAAA,IACnB;AAAA,EACF;AACF;AAGA,SAAS,eACP,WACA,UACA,QAC2B;AAC3B,MAAI,CAAC,aAAa,UAAU,WAAW,EAAG,QAAO;AAEjD,QAAM,SAAwB,CAAC;AAC/B,WAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,UAAM,KAAK,UAAU,CAAC;AACtB,UAAM,UAAU,IAAI,UAAU,aAAa;AAC3C,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,QAAQ;AACN,YAAM,UAAU,QAAQ,MAAM,GAAG,GAAG;AACpC,YAAM,IAAI,iBAAiB,gCAAgC,OAAO,IAAI;AAAA,QACpE;AAAA,QACA,MAAM,SAAS,MAAM,GAAG,GAAG;AAAA,MAC7B,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,MACV,IAAI,IAAI,MAAM,QAAQ,CAAC;AAAA,MACvB,MAAM,IAAI,UAAU,QAAQ;AAAA,MAC5B,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGA,SAAS,cAAc,IAA8C;AACnE,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAGA,SAAS,SAAS,GAIO;AACvB,MAAI,CAAC,EAAG,QAAO;AACf,SAAO;AAAA,IACL,cAAc,EAAE,iBAAiB;AAAA,IACjC,kBAAkB,EAAE,qBAAqB;AAAA,IACzC,aAAa,EAAE,gBAAgB;AAAA,EACjC;AACF;AAGA,SAAS,eAAe,OAA8B;AACpD,QAAM,YAAsB,CAAC;AAC7B,aAAW,QAAQ,MAAM,MAAM,IAAI,GAAG;AAEpC,QAAI,SAAS,MAAM,KAAK,WAAW,GAAG,EAAG;AACzC,QAAI,KAAK,WAAW,OAAO,GAAG;AAC5B,gBAAU,KAAK,KAAK,MAAM,CAAC,EAAE,QAAQ,MAAM,EAAE,CAAC;AAAA,IAChD;AAAA,EAEF;AACA,MAAI,UAAU,WAAW,EAAG,QAAO;AACnC,SAAO,UAAU,KAAK,IAAI;AAC5B;AAGA,SAAS,mBACP,cACA,IACM;AACN,QAAM,MAAM,GAAG,SAAS;AACxB,QAAM,MAAM,aAAa,IAAI,GAAG,KAAK,EAAE,YAAY,GAAG;AACtD,MAAI,GAAG,GAAI,KAAI,KAAK,GAAG;AACvB,MAAI,GAAG,UAAU,KAAM,KAAI,OAAO,GAAG,SAAS;AAC9C,MAAI,GAAG,UAAU,UAAW,KAAI,cAAc,GAAG,SAAS;AAC1D,eAAa,IAAI,KAAK,GAAG;AAC3B;AAGA,SAAS,oBACP,cACA,cACA,OACgB;AAChB,QAAM,YAA2B,CAAC;AAClC,MAAI,aAAa,OAAO,GAAG;AACzB,UAAM,UAAU,MAAM,KAAK,aAAa,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACpE,eAAW,OAAO,SAAS;AACzB,YAAM,MAAM,aAAa,IAAI,GAAG;AAEhC,UAAI,CAAC,IAAI,MAAM,CAAC,IAAI,KAAM;AAC1B,UAAI;AACJ,UAAI;AACF,eAAO,IAAI,aAAa,KAAK,MAAM,IAAI,UAAU,IAAI,CAAC;AAAA,MACxD,QAAQ;AACN,cAAM,UAAU,IAAI,WAAW,MAAM,GAAG,GAAG;AAC3C,cAAM,IAAI,iBAAiB,gCAAgC,OAAO,IAAI;AAAA,UACpE,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AACA,gBAAU,KAAK;AAAA,QACb,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,QAAwB,CAAC;AAC/B,MAAI,UAAU,SAAS,EAAG,OAAM,YAAY;AAC5C,MAAI,aAAc,OAAM,eAAe;AACvC,MAAI,MAAO,OAAM,QAAQ;AACzB,SAAO;AACT;;;AC1SO,SAAS,eAAe,QAAgC;AAC7D,UAAQ,OAAO,UAAU;AAAA,IACvB,KAAK;AACH,aAAO,qBAAqB,MAAM;AAAA,IACpC;AACE,YAAM,IAAI,MAAM,6BAA6B,OAAO,OAAO,QAAQ,CAAC,EAAE;AAAA,EAC1E;AACF;;;AC/EO,IAAM,iBAAN,cAA6B,MAAM;AAAA;AAAA,EAE/B;AAAA,EAET,YAAY,SAAiB,UAAkB;AAC7C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;AAGA,IAAM,oBAAoB;AAG1B,SAAS,gBAAgB,QAAyB;AAChD,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,MAAI,WAAW,OAAW,QAAO;AACjC,SAAO,KAAK,UAAU,MAAM;AAC9B;AAGA,SAAS,eAAe,KAAsB;AAC5C,MAAI,eAAe,MAAO,QAAO,IAAI;AACrC,SAAO,OAAO,GAAG;AACnB;AAGA,SAAS,gBAAgB,GAAyB,GAAuB;AACvE,MAAI,CAAC,EAAG,QAAO,EAAE,GAAG,EAAE;AACtB,SAAO;AAAA,IACL,cAAc,EAAE,eAAe,EAAE;AAAA,IACjC,kBAAkB,EAAE,mBAAmB,EAAE;AAAA,IACzC,aAAa,EAAE,cAAc,EAAE;AAAA,EACjC;AACF;AAGA,SAAS,qBAAqB,OAAe,cAAqC;AAChF,QAAM,WAAyB,CAAC;AAChC,MAAI,cAAc;AAChB,aAAS,KAAK,EAAE,MAAM,UAAU,SAAS,aAAa,CAAC;AAAA,EACzD;AACA,WAAS,KAAK,EAAE,MAAM,QAAQ,SAAS,MAAM,CAAC;AAC9C,SAAO;AACT;AAGA,SAAS,mBAAmB,QAAyB;AACnD,SAAO;AAAA,IACL,OAAO,OAAO;AAAA,IACd,OAAO,OAAO;AAAA,IACd,aAAa,OAAO;AAAA,IACpB,WAAW,OAAO;AAAA,EACpB;AACF;AAeA,eAAsB,UAAU,OAAe,QAAmD;AAChG,QAAM,WAAW,qBAAqB,OAAO,OAAO,YAAY;AAChE,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,SAAS,mBAAmB,MAAM;AACxC,MAAI;AACJ,MAAI,QAAQ;AAEZ,SAAO,QAAQ,UAAU;AACvB;AAEA,UAAM,WAAW,MAAM,OAAO,SAAS,SAAS;AAAA,MAC9C,UAAU,CAAC,GAAG,QAAQ;AAAA,MACtB,GAAG;AAAA,IACL,CAAC;AAED,QAAI,SAAS,OAAO;AAClB,mBAAa,gBAAgB,YAAY,SAAS,KAAK;AAAA,IACzD;AAGA,aAAS,KAAK,SAAS,OAAO;AAG9B,QAAI,SAAS,eAAe,gBAAgB,CAAC,SAAS,QAAQ,WAAW;AACvE,aAAO;AAAA,QACL,SAAS,SAAS,QAAQ;AAAA,QAC1B;AAAA,QACA;AAAA,QACA,YAAY,SAAS;AAAA,QACrB,OAAO;AAAA,MACT;AAAA,IACF;AAGA,eAAW,YAAY,SAAS,QAAQ,WAAW;AACjD,UAAI;AACJ,UAAI;AACF,cAAM,SAAS,MAAM,OAAO,YAAY,SAAS,MAAM,SAAS,SAAS;AACzE,oBAAY,gBAAgB,MAAM;AAAA,MACpC,SAAS,KAAK;AACZ,oBAAY,eAAe,GAAG;AAAA,MAChC;AACA,eAAS,KAAK;AAAA,QACZ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,SAAS;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR,cAAc,QAAQ;AAAA,IACtB;AAAA,EACF;AACF;AAeA,gBAAuB,gBACrB,OACA,QACqC;AACrC,QAAM,WAAW,qBAAqB,OAAO,OAAO,YAAY;AAChE,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,SAAS,mBAAmB,MAAM;AACxC,MAAI;AACJ,MAAI,QAAQ;AAEZ,SAAO,QAAQ,UAAU;AACvB;AAEA,QAAI,cAAc;AAClB,QAAI;AACJ,QAAI;AAEJ,qBAAiB,SAAS,OAAO,SAAS,OAAO;AAAA,MAC/C,UAAU,CAAC,GAAG,QAAQ;AAAA,MACtB,GAAG;AAAA,IACL,CAAC,GAAG;AAEF,UAAI,OAAO,MAAM,iBAAiB,YAAY,MAAM,aAAa,SAAS,GAAG;AAC3E,uBAAe,MAAM;AACrB,cAAM,EAAE,cAAc,MAAM,aAAa;AAAA,MAC3C;AAGA,UAAI,MAAM,aAAa,MAAM,UAAU,SAAS,GAAG;AACjD,oBAAY,MAAM;AAAA,MACpB;AAGA,UAAI,MAAM,cAAc;AACtB,uBAAe,MAAM;AAAA,MACvB;AACA,UAAI,MAAM,OAAO;AACf,qBAAa,gBAAgB,YAAY,MAAM,KAAK;AAAA,MACtD;AAAA,IACF;AAGA,UAAM,mBAA+B;AAAA,MACnC,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AACA,QAAI,WAAW;AACb,uBAAiB,YAAY;AAAA,IAC/B;AACA,aAAS,KAAK,gBAAgB;AAG9B,QAAI,iBAAiB,gBAAgB,CAAC,WAAW;AAC/C,YAAM;AAAA,QACJ,MAAM;AAAA,UACJ,SAAS;AAAA,UACT;AAAA,UACA,YAAY,gBAAgB;AAAA,UAC5B,OAAO;AAAA,QACT;AAAA,MACF;AACA;AAAA,IACF;AAGA,eAAW,YAAY,WAAW;AAChC,YAAM,EAAE,UAAU,EAAE,MAAM,SAAS,MAAM,WAAW,SAAS,UAAU,EAAE;AAEzE,UAAI;AACJ,UAAI;AACF,cAAM,SAAS,MAAM,OAAO,YAAY,SAAS,MAAM,SAAS,SAAS;AACzE,oBAAY,gBAAgB,MAAM;AAAA,MACpC,SAAS,KAAK;AACZ,oBAAY,eAAe,GAAG;AAAA,MAChC;AAEA,YAAM,EAAE,YAAY,EAAE,MAAM,SAAS,MAAM,QAAQ,UAAU,EAAE;AAE/D,eAAS,KAAK;AAAA,QACZ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,SAAS;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR,cAAc,QAAQ;AAAA,IACtB;AAAA,EACF;AACF;;;ACzSA,IAAM,0BAA0B;AA2EzB,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAOO,IAAM,sBAAN,cAAkC,WAAW;AAAA;AAAA,EAEzC;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,UAAkB,cAAsB;AAClD;AAAA,MACE,iDAAiD,YAAY,oBAAoB,QAAQ;AAAA,IAC3F;AACA,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,eAAe;AAAA,EACtB;AACF;AAOO,IAAM,QAAN,MAAM,OAAM;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,cAAc,oBAAI,IAA8C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjF,YAAY,MAAiB,QAAgB,GAAG;AAC9C,SAAK,OAAO;AACZ,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,IAAI,OAAyC;AACjD,UAAM,SAAS,MAAM,KAAK,gBAAgB;AAC1C,WAAO,UAAU,OAAO,MAAM;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OAAO,OAAO,OAAoD;AAChE,UAAM,SAAS,MAAM,KAAK,gBAAgB;AAC1C,WAAO,gBAAgB,OAAO,MAAM;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,SAA0C;AACxC,UAAM,OAAO,KAAK,KAAK,SAAS,KAAK,KAAK,SAAS;AACnD,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,SAAS,KAAK,IAAI;AAAA,MACxB,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAc,cAAc,MAA+D;AACzF,QAAI,CAAC,KAAK,KAAK,kBAAmB,QAAO;AACzC,QAAI,KAAK,YAAY,IAAI,KAAK,IAAI,GAAG;AACnC,aAAO,KAAK,YAAY,IAAI,KAAK,IAAI;AAAA,IACvC;AACA,UAAM,WAAW,MAAM,KAAK,KAAK,kBAAkB,IAAI;AACvD,SAAK,YAAY,IAAI,KAAK,MAAM,QAAQ;AACxC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,kBAA4C;AACxD,UAAM,OAAO,KAAK,KAAK,SAAS,KAAK,KAAK,SAAS;AACnD,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,WAAW,UAAU,KAAK,KAAK,SAAS,qBAAqB;AAAA,IACzE;AAEA,UAAM,QAAQ,MAAM,KAAK,qBAAqB;AAE9C,WAAO;AAAA,MACL,UAAU,KAAK,KAAK;AAAA,MACpB,cAAc,KAAK;AAAA,MACnB,OAAO,KAAK;AAAA,MACZ,UAAU,KAAK,YAAY,KAAK,KAAK,QAAQ;AAAA,MAC7C;AAAA,MACA,aAAa,OAAO,MAAM,SAAS,KAAK,YAAY,MAAM,IAAI;AAAA,IAChE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAc,uBAAqD;AACjE,UAAM,cAAc,oBAAI,IAA+B;AAGvD,eAAW,QAAQ,KAAK,KAAK,kBAAkB,KAAK,KAAK,SAAS,GAAG;AACnE,UAAI,YAAY,IAAI,KAAK,IAAI,EAAG;AAChC,YAAM,YAAY,MAAM,KAAK,cAAc,IAAI;AAC/C,kBAAY,IAAI,KAAK,MAAM;AAAA,QACzB,MAAM,KAAK;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,OAAO,WAAW,cAAc,EAAE,MAAM,SAAS;AAAA,MACnD,CAAC;AAAA,IACH;AAGA,UAAM,eAAe,KAAK,KAAK,QAAQ;AACvC,QAAI,cAAc;AAChB,iBAAW,YAAY,cAAc;AACnC,YAAI,YAAY,IAAI,QAAQ,EAAG;AAC/B,cAAM,OAAO,KAAK,KAAK,QAAQ,QAAQ;AACvC,YAAI,CAAC,KAAM;AACX,cAAM,YAAY,MAAM,KAAK,cAAc,IAAI;AAC/C,oBAAY,IAAI,KAAK,MAAM;AAAA,UACzB,MAAM,KAAK;AAAA,UACX,aAAa,KAAK;AAAA,UAClB,OAAO,WAAW,cAAc,EAAE,MAAM,SAAS;AAAA,QACnD,CAAC;AAAA,MACH;AAAA,IACF;AAGA,eAAW,YAAY,KAAK,KAAK,iBAAiB,KAAK,KAAK,SAAS,GAAG;AACtE,YAAM,OAAO,SAAS,SAAS,IAAI;AACnC,UAAI,YAAY,IAAI,IAAI,EAAG;AAC3B,kBAAY,IAAI,MAAM;AAAA,QACpB;AAAA,QACA,aAAa,SAAS;AAAA,QACtB,OAAO,EAAE,MAAM,SAAS;AAAA,MAC1B,CAAC;AAAA,IACH;AAEA,WAAO,MAAM,KAAK,YAAY,OAAO,CAAC;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAc,YAAY,MAAc,MAAiD;AAEvF,QAAI,KAAK,WAAW,QAAQ,GAAG;AAC7B,aAAO,KAAK,gBAAgB,KAAK,MAAM,CAAC,GAAG,IAAI;AAAA,IACjD;AAGA,UAAM,OAAO,KAAK,KAAK,QAAQ,IAAI;AACnC,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,SAAS,IAAI,aAAa;AAAA,IAC5C;AAGA,UAAM,YAAY,MAAM,KAAK,cAAc,IAAI;AAC/C,QAAI,WAAW;AACf,QAAI,WAAW;AACb,YAAM,SAAS,UAAU,SAAS,IAAI;AACtC,UAAI,CAAC,OAAO,IAAI;AAEd,eAAO,EAAE,OAAO,OAAO,MAAM;AAAA,MAC/B;AACA,iBAAW,OAAO,SAAS;AAAA,IAC7B;AAEA,UAAM,MAAM,MAAM,KAAK,KAAK,eAAe,KAAK,UAAU,KAAK,YAAY;AAC3E,WAAO,MAAM,IAAI,QAAQ,QAAQ;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,gBAAgB,SAAiB,MAAiD;AAC9F,UAAM,WAAW,KAAK,QAAQ;AAC9B,UAAM,WAAW,KAAK,KAAK,QAAQ,iBAAiB;AACpD,QAAI,WAAW,UAAU;AACvB,YAAM,IAAI,oBAAoB,UAAU,QAAQ;AAAA,IAClD;AAGA,UAAM,UAAqB,EAAE,GAAG,KAAK,MAAM,WAAW,QAAQ;AAC9D,UAAM,WAAW,IAAI,OAAM,SAAS,QAAQ;AAG5C,UAAM,OAAO,KAAK,KAAK,SAAS,OAAO;AACvC,QAAI,MAAM,QAAQ;AAChB,YAAM,MAAM,MAAM,KAAK,KAAK,gBAAgB,KAAK,UAAU,KAAK,WAAW,KAAK,MAAM;AACtF,UAAI,IAAI,KAAK;AACX,eAAO,MAAM,IAAI,IAAI,IAAI;AAAA,MAC3B;AAAA,IACF;AAGA,UAAM,SAAS,MAAM,SAAS,IAAI,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,IAAI,CAAC;AACxF,WAAO,OAAO;AAAA,EAChB;AACF;;;ACrWA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP,SAAS,SAAS;AAgBlB,eAAe,sBACb,MACA,SAC2C;AAC3C,QAAM,YAAY,MAAM,eAAe,MAAM,OAAO;AACpD,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,SAAS,UAAU;AACzB,SAAO;AAAA,IACL,YAAY,EAAE,aAAa,MAAM;AAAA,IACjC,UAAU,CAAC,UAAU;AACnB,YAAM,SAAS,OAAO,UAAU,KAAK;AACrC,UAAI,OAAO,SAAS;AAClB,eAAO,EAAE,IAAI,MAAe,OAAO,OAAO,KAAgC;AAAA,MAC5E;AACA,aAAO,EAAE,IAAI,OAAgB,OAAO,OAAO,MAAM,QAAQ;AAAA,IAC3D;AAAA,EACF;AACF;AAQA,SAAS,gBAAgB,KAA6C;AACpE,QAAM,MAAM,IAAI,QAAQ;AACxB,MAAI,QAAQ,UAAa,QAAQ,KAAM,QAAO;AAC9C,SAAO;AACT;AAQA,IAAM,cAA2B;AAAA,EAC/B,MAAM;AAAA,EACN,MAAM,KAA0B;AAC9B,UAAM,cAAc,gBAAgB,GAAG;AAGvC,QAAI,CAAC,aAAa,KAAK;AACrB,cAAQ;AAAA,QACN;AAAA,MACF;AACA;AAAA,IACF;AACA,QAAI,CAAC,YAAY,cAAc;AAC7B,cAAQ;AAAA,QACN;AAAA,MACF;AACA;AAAA,IACF;AAGA,UAAM,WAAW,eAAe,YAAY,GAAG;AAG/C,UAAM,gBAAoC;AAAA,MACxC,UAAU,YAAY;AAAA,MACtB,eAAe,YAAY;AAAA,MAC3B,cAAc,YAAY;AAAA,IAC5B;AAEA,UAAM,UAAU,IAAI;AACpB,UAAM,eAAe,YAAY;AAEjC,UAAM,oBAAoB,CAAC,SAAuB,sBAAsB,MAAM,OAAO;AAIrF,+BAA2B,MAAM;AAC/B,aAAO,IAAI,MAAM;AAAA,QACf;AAAA,QACA,WAAW;AAAA,QACX;AAAA,QACA,QAAQ;AAAA;AAAA;AAAA,QAGR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAEA,gBAAgB,CAAC,UAAU,iBAAiB,eAAe,UAAU,cAAc,OAAO;AAAA,QAC1F,iBAAiB,CAAC,UAAU,WAAW,WACrC,gBAAgB,UAAU,WAAW,QAAQ,OAAO;AAAA;AAAA,QAEtD;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAED,YAAQ;AAAA,MACN,kCAAkC,YAAY;AAAA,IAChD;AAAA,EACF;AACF;AAEA,IAAO,iBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/providers/openai.ts","../src/provider.ts","../src/reactLoop.ts","../src/agent.ts","../src/plugin.ts"],"sourcesContent":["import type { LlmConfig } from '@faapi/faapi';\nimport type {\n LLMCompleteRequest,\n LLMMessage,\n LLMProvider,\n LLMResponse,\n LLMStopReason,\n LLMStreamChunk,\n LLMToolCall,\n LLMToolDefinition,\n LLMUsage,\n} from '../provider';\n\n/**\n * OpenAI 兼容 API 的 LLMProvider 实现\n *\n * 通过 `LlmConfig.baseURL` 可指向任意 OpenAI 兼容中转服务\n * (官方 OpenAI / Azure OpenAI / LiteLLM / one-api / 自建网关)。\n *\n * 详见 [openai.md](./openai.md)。\n */\n\n/** OpenAI 默认 API 端点(未设 baseURL 时使用) */\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\n\n/**\n * OpenAI 已知保留字段集合(这些字段在 LlmConfig 中有专门处理,不算\"额外透传\")\n *\n * - `provider` / `apiKey` / `baseURL` —— LlmConfig 结构字段\n * - `model` —— 由 `request.model` 或 `config.models` 第一个 key 决定,不由 config 透传\n * - `models` —— 嵌套级联的 model 配置映射,非 OpenAI API 字段\n */\nconst RESERVED_CONFIG_KEYS = new Set(['provider', 'apiKey', 'model', 'baseURL', 'models']);\n\n/**\n * LLM Provider 错误\n *\n * 含 HTTP 状态码(网络错误为 `undefined`)和响应体摘要,\n * 业务方可通过 `instanceof LLMProviderError` 区分 LLM 错误与其他错误。\n */\nexport class LLMProviderError extends Error {\n /** HTTP 状态码(网络错误 / JSON 解析错误为 undefined) */\n readonly status?: number;\n /** 响应体摘要(前 500 字符,便于诊断) */\n readonly body?: string;\n\n constructor(message: string, options?: { status?: number; body?: string; cause?: unknown }) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = 'LLMProviderError';\n this.status = options?.status;\n this.body = options?.body;\n }\n}\n\n/** 内部存储:OpenAI chat completions 请求体(构造阶段) */\ninterface OpenAIRequestBody {\n model?: string;\n messages: unknown[];\n tools?: unknown[];\n stream?: boolean;\n temperature?: number;\n max_tokens?: number;\n [key: string]: unknown;\n}\n\n/** 内部存储:OpenAI tool_call 项(非流式 + 流式统一,index 仅流式有) */\ninterface OpenAIToolCall {\n index?: number;\n id?: string;\n type?: string;\n function?: { name?: string; arguments?: string };\n}\n\n/** 内部存储:OpenAI usage 字段 */\ninterface OpenAIUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}\n\n/** 内部存储:OpenAI chat completions 响应 */\ninterface OpenAIResponseJson {\n choices?: Array<{\n index?: number;\n message?: {\n role?: string;\n content?: string | null;\n tool_calls?: OpenAIToolCall[];\n };\n finish_reason?: string | null;\n }>;\n usage?: OpenAIUsage;\n}\n\n/** 内部存储:OpenAI streaming chunk */\ninterface OpenAIStreamChunkJson {\n choices?: Array<{\n delta?: {\n content?: string | null;\n tool_calls?: OpenAIToolCall[];\n };\n finish_reason?: string | null;\n }>;\n usage?: OpenAIUsage;\n}\n\n/** tool call 累积器(stream 模式按 index 累积 args 字符串) */\ninterface ToolCallAccumulator {\n id?: string;\n name?: string;\n argsString: string;\n}\n\n/**\n * 创建 OpenAI 兼容 LLMProvider\n *\n * @param config LLM 提供方配置(apiKey / baseURL / models + provider 级透传字段,\n * model 级字段在 `models[modelName]` 下覆盖 provider 级同名字段)\n * @returns LLMProvider 实例(含 complete + stream 方法)\n */\nexport function createOpenAIProvider(config: LlmConfig): LLMProvider {\n const baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const apiKey = config.apiKey;\n\n /**\n * 构造 OpenAI chat completions 请求体\n *\n * 字段优先级(高 → 低):\n * 1. `request.temperature` / `request.maxTokens` —— request 级覆盖\n * 2. `config.models[modelName][key]` —— model 级覆盖(如 model 级 temperature)\n * 3. `config[key]` —— provider 级透传(如 provider 级 temperature)\n *\n * `model` 由 `request.model` 或 `config.models` 第一个 key 决定,\n * 不从 config 透传(避免 config 级 `model` 字段覆盖已解析的 model 名)。\n */\n function buildRequestBody(request: LLMCompleteRequest): OpenAIRequestBody {\n // 确定 model 名:request 优先,否则取 config.models 第一个 key\n const modelName = request.model ?? Object.keys(config.models)[0];\n const modelConfig = modelName ? config.models[modelName] : undefined;\n\n const body: OpenAIRequestBody = {\n model: modelName,\n messages: request.messages.map(toOpenAIMessage),\n };\n\n if (request.tools && request.tools.length > 0) {\n body.tools = request.tools.map(toOpenAITool);\n }\n\n // 合并 config 透传字段:provider 级 + model 级覆盖(model 级优先)\n // 跳过 RESERVED_CONFIG_KEYS(结构字段 + model 名 + models 嵌套配置)\n const mergedConfig: Record<string, unknown> = {};\n for (const key of Object.keys(config)) {\n if (RESERVED_CONFIG_KEYS.has(key)) continue;\n const value = (config as Record<string, unknown>)[key];\n if (value !== undefined) {\n mergedConfig[key] = value;\n }\n }\n if (modelConfig) {\n for (const key of Object.keys(modelConfig)) {\n if (RESERVED_CONFIG_KEYS.has(key)) continue;\n const value = (modelConfig as Record<string, unknown>)[key];\n if (value !== undefined) {\n mergedConfig[key] = value; // 覆盖 provider 级同名字段\n }\n }\n }\n\n // 应用透传字段到 body(跳过已设置的结构字段 model/messages/tools)\n for (const key of Object.keys(mergedConfig)) {\n const value = mergedConfig[key];\n if (value !== undefined && !(key in body)) {\n body[key] = value;\n }\n }\n\n // request 级覆盖(最高优先级)\n if (request.temperature !== undefined) body.temperature = request.temperature;\n if (request.maxTokens !== undefined) body.max_tokens = request.maxTokens;\n\n return body;\n }\n\n /** 构造请求 headers */\n function buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (apiKey) headers.Authorization = `Bearer ${apiKey}`;\n return headers;\n }\n\n /** 调 fetch,统一捕获网络错误 */\n async function safeFetch(url: string, init: RequestInit): Promise<Response> {\n try {\n return await fetch(url, init);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new LLMProviderError(`Network error: ${reason}`, { cause: err });\n }\n }\n\n /** 校验 HTTP 响应状态,非 2xx 抛 LLMProviderError */\n async function ensureOk(response: Response): Promise<string> {\n if (response.ok) return '';\n const bodyText = await response.text();\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError(`HTTP ${response.status}: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n // ─── complete ──────────────────────────────────────\n\n async function complete(request: LLMCompleteRequest): Promise<LLMResponse> {\n const url = `${baseURL}/chat/completions`;\n const body = buildRequestBody(request);\n const init: RequestInit = {\n method: 'POST',\n headers: buildHeaders(),\n body: JSON.stringify(body),\n };\n\n const response = await safeFetch(url, init);\n await ensureOk(response);\n\n const bodyText = await response.text();\n let json: OpenAIResponseJson;\n try {\n json = JSON.parse(bodyText) as OpenAIResponseJson;\n } catch {\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError(`Invalid JSON response: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n const choice = json.choices?.[0];\n const msg = choice?.message;\n if (!choice || !msg) {\n const excerpt = bodyText.slice(0, 500);\n throw new LLMProviderError('Empty choices in response', {\n status: response.status,\n body: excerpt,\n });\n }\n\n const content = msg.content ?? '';\n const toolCalls = parseToolCalls(msg.tool_calls, bodyText, response.status);\n\n return {\n message: {\n role: 'assistant',\n content,\n toolCalls,\n },\n stopReason: mapStopReason(choice.finish_reason ?? undefined),\n usage: mapUsage(json.usage),\n };\n }\n\n // ─── stream ──────────────────────────────────────\n\n async function* stream(request: LLMCompleteRequest): AsyncIterable<LLMStreamChunk> {\n const url = `${baseURL}/chat/completions`;\n const body = buildRequestBody(request);\n body.stream = true;\n\n const init: RequestInit = {\n method: 'POST',\n headers: buildHeaders(),\n body: JSON.stringify(body),\n };\n\n const response = await safeFetch(url, init);\n await ensureOk(response);\n\n if (!response.body) {\n throw new LLMProviderError('Response body is null (streaming unsupported)', {\n status: response.status,\n });\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n /** tool call 累积器,按 index 聚合 args 字符串 */\n const accumulators = new Map<number, ToolCallAccumulator>();\n let finishReason: LLMStopReason | undefined;\n let usage: LLMUsage | undefined;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n // SSE 事件以 \\n\\n 分隔\n let sep: number;\n while ((sep = buffer.indexOf('\\n\\n')) >= 0) {\n const eventStr = buffer.slice(0, sep);\n buffer = buffer.slice(sep + 2);\n\n const data = extractSSEData(eventStr);\n if (data === null) continue; // 注释行 / 心跳行 / 非 data 行\n\n if (data === '[DONE]') {\n // emit 最终 chunk(含累积的 toolCalls + finishReason + usage)\n yield finalizeStreamChunk(accumulators, finishReason, usage);\n return;\n }\n\n let chunk: OpenAIStreamChunkJson;\n try {\n chunk = JSON.parse(data) as OpenAIStreamChunkJson;\n } catch {\n const excerpt = data.slice(0, 500);\n throw new LLMProviderError(`Invalid SSE chunk: ${excerpt}`, {\n status: response.status,\n body: excerpt,\n });\n }\n\n const delta = chunk.choices?.[0]?.delta;\n if (delta) {\n // 内容增量\n if (typeof delta.content === 'string' && delta.content.length > 0) {\n yield { deltaContent: delta.content };\n }\n // tool_calls 增量累积\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n accumulateToolCall(accumulators, tc);\n }\n }\n }\n\n const fr = chunk.choices?.[0]?.finish_reason;\n if (fr) finishReason = mapStopReason(fr);\n if (chunk.usage) usage = mapUsage(chunk.usage);\n }\n }\n\n // 流自然结束(无 [DONE]),emit 最终 chunk\n yield finalizeStreamChunk(accumulators, finishReason, usage);\n } finally {\n reader.releaseLock();\n }\n }\n\n return { complete, stream };\n}\n\n// ─── 辅助函数 ──────────────────────────────────────\n\n/** LLMMessage → OpenAI 消息格式 */\nfunction toOpenAIMessage(msg: LLMMessage): unknown {\n const out: Record<string, unknown> = {\n role: msg.role,\n content: msg.content,\n };\n if (msg.toolCallId !== undefined) out.tool_call_id = msg.toolCallId;\n if (msg.toolCalls !== undefined && msg.toolCalls.length > 0) {\n out.tool_calls = msg.toolCalls.map((tc) => ({\n id: tc.id,\n type: 'function',\n function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },\n }));\n }\n return out;\n}\n\n/** LLMToolDefinition → OpenAI tool 定义格式 */\nfunction toOpenAITool(tool: LLMToolDefinition): unknown {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.input,\n },\n };\n}\n\n/** OpenAI tool_calls 数组 → LLMToolCall[],JSON.parse 每个 arguments */\nfunction parseToolCalls(\n toolCalls: OpenAIToolCall[] | undefined,\n bodyText: string,\n status: number,\n): LLMToolCall[] | undefined {\n if (!toolCalls || toolCalls.length === 0) return undefined;\n\n const result: LLMToolCall[] = [];\n for (let i = 0; i < toolCalls.length; i++) {\n const tc = toolCalls[i];\n const argsStr = tc?.function?.arguments ?? '{}';\n let args: Record<string, unknown>;\n try {\n args = JSON.parse(argsStr);\n } catch {\n const excerpt = argsStr.slice(0, 500);\n throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {\n status,\n body: bodyText.slice(0, 500),\n });\n }\n result.push({\n id: tc?.id ?? `call_${i}`,\n name: tc?.function?.name ?? '',\n arguments: args,\n });\n }\n return result;\n}\n\n/** OpenAI finish_reason → LLMStopReason */\nfunction mapStopReason(fr: string | undefined | null): LLMStopReason {\n switch (fr) {\n case 'stop':\n return 'stop';\n case 'tool_calls':\n return 'tool_calls';\n case 'length':\n return 'length';\n case 'content_filter':\n return 'content_filter';\n default:\n return 'other';\n }\n}\n\n/** OpenAI usage → LLMUsage */\nfunction mapUsage(u?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}): LLMUsage | undefined {\n if (!u) return undefined;\n return {\n promptTokens: u.prompt_tokens ?? 0,\n completionTokens: u.completion_tokens ?? 0,\n totalTokens: u.total_tokens ?? 0,\n };\n}\n\n/** 从 SSE 事件块提取 data: 行内容(多行用 \\n 拼接) */\nfunction extractSSEData(event: string): string | null {\n const dataLines: string[] = [];\n for (const line of event.split('\\n')) {\n // 跳过空行 / 注释行 / 心跳行\n if (line === '' || line.startsWith(':')) continue;\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n // 其他字段(event: / id: / retry:)忽略,OpenAI 不用\n }\n if (dataLines.length === 0) return null;\n return dataLines.join('\\n');\n}\n\n/** 累积 tool call chunk 到按 index 的 Map */\nfunction accumulateToolCall(\n accumulators: Map<number, ToolCallAccumulator>,\n tc: OpenAIToolCall,\n): void {\n const idx = tc.index ?? 0;\n const acc = accumulators.get(idx) ?? { argsString: '' };\n if (tc.id) acc.id = tc.id;\n if (tc.function?.name) acc.name = tc.function.name;\n if (tc.function?.arguments) acc.argsString += tc.function.arguments;\n accumulators.set(idx, acc);\n}\n\n/** 流结束时把累积器转为 LLMToolCall[] + finishReason + usage 一并 emit */\nfunction finalizeStreamChunk(\n accumulators: Map<number, ToolCallAccumulator>,\n finishReason: LLMStopReason | undefined,\n usage: LLMUsage | undefined,\n): LLMStreamChunk {\n const toolCalls: LLMToolCall[] = [];\n if (accumulators.size > 0) {\n const indices = Array.from(accumulators.keys()).sort((a, b) => a - b);\n for (const idx of indices) {\n const acc = accumulators.get(idx)!;\n // 跳过不完整(无 id 或 name)的累积,异常流不应阻止结束\n if (!acc.id || !acc.name) continue;\n let args: Record<string, unknown>;\n try {\n args = acc.argsString ? JSON.parse(acc.argsString) : {};\n } catch {\n const excerpt = acc.argsString.slice(0, 500);\n throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {\n body: excerpt,\n });\n }\n toolCalls.push({\n id: acc.id,\n name: acc.name,\n arguments: args,\n });\n }\n }\n\n const chunk: LLMStreamChunk = {};\n if (toolCalls.length > 0) chunk.toolCalls = toolCalls;\n if (finishReason) chunk.finishReason = finishReason;\n if (usage) chunk.usage = usage;\n return chunk;\n}\n","import type { LlmConfig } from '@faapi/faapi';\nimport { createOpenAIProvider } from './providers/openai';\n\n/**\n * LLM Provider 抽象层\n *\n * 统一 `complete` / `stream` 接口,屏蔽 OpenAI / Anthropic 等 LLM 服务差异。\n * [reactLoop](./reactLoop.md) 与 [Agent 类](./agent.md) 通过此接口调用 LLM,\n * 与具体 provider 解耦。\n *\n * 详见 [provider.md](./provider.md)。\n */\n\n/**\n * 对话消息\n *\n * 四种 role 与 OpenAI chat completions 一致:\n * - `system` —— 系统提示词(agent 的 systemPrompt)\n * - `user` —— 用户输入\n * - `assistant` —— LLM 回复(可能含 toolCalls)\n * - `tool` —— tool 执行结果(需带 toolCallId 标识对应哪个 tool_call)\n */\nexport interface LLMMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n /** 消息内容(assistant 角色 + tool_calls 时可能为空字符串) */\n content: string;\n /** role='tool' 时:对应的 tool_call ID(用于 LLM 关联 tool 结果) */\n toolCallId?: string;\n /** role='assistant' 时:LLM 请求的 tool 调用(reactLoop 据此执行 tool) */\n toolCalls?: LLMToolCall[];\n}\n\n/**\n * LLM 请求的 tool 调用\n *\n * 由 LLM 在 assistant 消息中返回。`arguments` 已 JSON.parse,\n * reactLoop 直接传给 tool 函数。\n */\nexport interface LLMToolCall {\n /** tool call ID(provider 分配,用于匹配 tool 结果) */\n id: string;\n /** tool 名(匹配 LLMToolDefinition.name) */\n name: string;\n /** tool 参数(已 JSON.parse 的对象) */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool 定义\n *\n * 由 [reactLoop](./reactLoop.md) 从 [toolRegistry](../../faapi/src/injection/toolRegistry.md)\n * + [agentRegistry.resolveSubAgents](../../faapi/src/injection/agentRegistry.md) 组装:\n * - 常规 tool:`input` 来自 AST 提取的 zod schema(JSON Schema 形式)\n * - agent-as-tool:`input` 为自由 schema(agent 参数开放)\n */\nexport interface LLMToolDefinition {\n /** tool 名(如 `weather.getWeather` 或 `agent.researcher`) */\n name: string;\n /** tool 描述(对 LLM 可见,引导 LLM 选择调用) */\n description?: string;\n /** JSON Schema 对象(描述 tool 参数结构) */\n input: Record<string, unknown>;\n}\n\n/**\n * complete / stream 的入参\n *\n * `model` / `temperature` / `maxTokens` 优先级高于 [LlmConfig](../../faapi/src/config/configTypes.md)\n * (agent 自身 `config.model` 覆盖全局默认)。\n */\nexport interface LLMCompleteRequest {\n /** 对话消息(含历史 + 当前轮) */\n messages: LLMMessage[];\n /** 可用 tool 列表(未提供时 LLM 不会发起 tool_call) */\n tools?: LLMToolDefinition[];\n /** 覆盖 LlmConfig.model(agent 自身配置优先) */\n model?: string;\n /** 采样温度(0~2) */\n temperature?: number;\n /** 最大生成 token 数 */\n maxTokens?: number;\n}\n\n/**\n * LLM 响应的停止原因\n *\n * - `stop` —— LLM 主动结束(自然结束 / 遇到 stop 序列)\n * - `tool_calls` —— LLM 请求调用 tool(reactLoop 据此进入下一轮)\n * - `length` —— 达到 max_tokens 上限\n * - `content_filter` —— 内容过滤触发\n * - `other` —— 其他原因(未识别的 finish_reason)\n */\nexport type LLMStopReason = 'stop' | 'tool_calls' | 'length' | 'content_filter' | 'other';\n\n/**\n * complete 的返回\n *\n * `message.toolCalls` 不为空时 stopReason 应为 `'tool_calls'`。\n */\nexport interface LLMResponse {\n /** assistant 消息(含 content + 可选 toolCalls) */\n message: LLMMessage;\n /** 停止原因(reactLoop 据此判断是否进入下一轮) */\n stopReason: LLMStopReason;\n /** token 用量(部分 provider 不返回) */\n usage?: LLMUsage;\n}\n\n/**\n * Token 用量\n */\nexport interface LLMUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n}\n\n/**\n * stream 的单个 chunk\n *\n * - 内容流:`deltaContent` 为增量 token\n * - tool 调用:累积完成后在最终 chunk 一并 emit `toolCalls`\n * - 结束:最终 chunk 含 `finishReason` + 可选 `usage`\n */\nexport interface LLMStreamChunk {\n /** 增量内容(streaming token) */\n deltaContent?: string;\n /** 累积完成的 tool 调用(在最终 chunk 出现) */\n toolCalls?: LLMToolCall[];\n /** 结束原因(只在最终 chunk 出现) */\n finishReason?: LLMStopReason;\n /** token 用量(部分 provider 在最终 chunk 提供) */\n usage?: LLMUsage;\n}\n\n/**\n * LLM Provider 抽象接口\n *\n * 实现方:\n * - [createOpenAIProvider](./providers/openai.md) —— OpenAI 兼容 API\n *\n * 消费方:\n * - [reactLoop](./reactLoop.md) Phase 3.3 —— 调 complete / stream 执行 ReAct 循环\n * - [Agent 类](./agent.md) Phase 3.4 —— 构造时持有 provider 实例\n */\nexport interface LLMProvider {\n /** 非流式:阻塞到 LLM 返回完整响应 */\n complete(request: LLMCompleteRequest): Promise<LLMResponse>;\n /** 流式:异步迭代 chunk(增量 token + tool call + 结束) */\n stream(request: LLMCompleteRequest): AsyncIterable<LLMStreamChunk>;\n}\n\n/**\n * LLM Provider 错误\n *\n * 由具体 provider 抛出,包含 HTTP 状态码(网络错误为 `undefined`)和响应体摘要。\n * 业务方可通过 `instanceof LLMProviderError` 区分 LLM 错误与其他错误。\n *\n * 重新从 [./providers/openai](./providers/openai.md) 导出,便于业务方从此模块统一捕获。\n */\nexport { LLMProviderError } from './providers/openai';\n\n/**\n * 按 `config.provider` 路由到对应的 LLM 适配器\n *\n * Phase 3.2 仅支持 `'openai'`,其他值抛错不静默降级(参考 AGENTS.md §6.3)。\n *\n * @param config LLM 提供方配置(来自 faapi.config.ts 的 `agent.llms[key]`)\n * @returns LLMProvider 实例\n * @throws {Error} 当 `config.provider` 不是已支持的值\n */\nexport function createProvider(config: LlmConfig): LLMProvider {\n switch (config.provider) {\n case 'openai':\n return createOpenAIProvider(config);\n default:\n throw new Error(`Unsupported LLM provider: ${String(config.provider)}`);\n }\n}\n","import type {\n LLMMessage,\n LLMProvider,\n LLMStopReason,\n LLMToolCall,\n LLMToolDefinition,\n LLMUsage,\n} from './provider';\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 */\nexport type ToolExecutor = (name: string, args: Record<string, unknown>) => Promise<unknown>;\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\n/**\n * 非流式循环结果\n */\nexport interface ReactLoopResult {\n /** 最终 assistant 消息内容 */\n content: string;\n /** 完整对话历史(system + user + assistant + tool 消息) */\n messages: LLMMessage[];\n /** 使用的轮数(含最终轮) */\n turns: number;\n /** 最终轮的停止原因 */\n stopReason: LLMStopReason;\n /** 累计 token 用量(多轮累加,provider 不返回时为 `undefined`) */\n usage?: LLMUsage;\n}\n\n/**\n * 流式循环的单个 chunk\n *\n * 每个 chunk 至多含一个字段:\n * - `deltaContent` — LLM 增量 token(多次 yield)\n * - `toolCall` — tool 开始执行\n * - `toolResult` — tool 执行完成\n * - `done` — 循环结束(只 yield 一次)\n */\nexport interface ReactLoopStreamChunk {\n /** LLM 增量 token */\n deltaContent?: string;\n /** tool 开始执行(LLM 请求调用 tool) */\n toolCall?: { name: string; arguments: Record<string, unknown> };\n /** tool 执行完成(含结果) */\n toolResult?: { name: string; result: string };\n /** 循环结束 */\n done?: {\n content: string;\n turns: number;\n stopReason: LLMStopReason;\n usage?: LLMUsage;\n };\n}\n\n/**\n * reactLoop 系统级错误\n *\n * 目前仅用于 `maxTurns` 超限。tool 执行错误不抛此类型——它们被 catch 后回传 LLM。\n */\nexport class ReactLoopError extends Error {\n /** 配置的 maxTurns 值 */\n readonly maxTurns: number;\n\n constructor(message: string, maxTurns: number) {\n super(message);\n this.name = 'ReactLoopError';\n this.maxTurns = maxTurns;\n }\n}\n\n/** 默认最大轮数 */\nconst DEFAULT_MAX_TURNS = 10;\n\n/** 把 tool 执行结果转为字符串(非 string 自动 JSON.stringify) */\nfunction stringifyResult(result: unknown): string {\n if (typeof result === 'string') return result;\n if (result === undefined) return '';\n return JSON.stringify(result);\n}\n\n/** 把 tool 执行错误转为字符串 */\nfunction stringifyError(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\n/** 累加 usage */\nfunction accumulateUsage(a: LLMUsage | undefined, b: LLMUsage): LLMUsage {\n if (!a) return { ...b };\n return {\n promptTokens: a.promptTokens + b.promptTokens,\n completionTokens: a.completionTokens + b.completionTokens,\n totalTokens: a.totalTokens + b.totalTokens,\n };\n}\n\n/** 构造初始 messages(可选 system + user input) */\nfunction buildInitialMessages(input: string, systemPrompt?: string): LLMMessage[] {\n const messages: LLMMessage[] = [];\n if (systemPrompt) {\n messages.push({ role: 'system', content: systemPrompt });\n }\n messages.push({ role: 'user', content: input });\n return messages;\n}\n\n/** 构造 LLM complete/stream 请求参数(除 messages 外的公共字段) */\nfunction buildRequestExtras(config: ReactLoopConfig) {\n return {\n tools: config.tools,\n model: config.model,\n temperature: config.temperature,\n maxTokens: config.maxTokens,\n };\n}\n\n// ─── reactLoop(非流式)──────────────────────────────\n\n/**\n * 执行 ReAct 循环(非流式)\n *\n * 反复调 `provider.complete()` → 执行 tool → 回传结果,直到 LLM 返回 `stop`(或其他非 `tool_calls` 原因)或超出 `maxTurns`。\n *\n * @param input 用户输入\n * @param config 循环配置\n * @returns 最终结果(content + messages + turns + stopReason + usage)\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.complete 抛错时立即传播\n */\nexport async function reactLoop(input: string, config: ReactLoopConfig): Promise<ReactLoopResult> {\n const messages = buildInitialMessages(input, config.systemPrompt);\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n const extras = buildRequestExtras(config);\n let totalUsage: LLMUsage | undefined;\n let turns = 0;\n\n while (turns < maxTurns) {\n turns++;\n\n const response = await config.provider.complete({\n messages: [...messages],\n ...extras,\n });\n\n if (response.usage) {\n totalUsage = accumulateUsage(totalUsage, response.usage);\n }\n\n // 把 assistant 消息加入历史\n messages.push(response.message);\n\n // 非 tool_calls → 循环结束\n if (response.stopReason !== 'tool_calls' || !response.message.toolCalls) {\n return {\n content: response.message.content,\n messages,\n turns,\n stopReason: response.stopReason,\n usage: totalUsage,\n };\n }\n\n // 执行每个 tool call\n for (const toolCall of response.message.toolCalls) {\n let resultStr: string;\n try {\n const result = await config.executeTool(toolCall.name, toolCall.arguments);\n resultStr = stringifyResult(result);\n } catch (err) {\n resultStr = stringifyError(err);\n }\n messages.push({\n role: 'tool',\n content: resultStr,\n toolCallId: toolCall.id,\n });\n }\n }\n\n throw new ReactLoopError(\n `Max turns (${maxTurns}) exceeded — agent did not converge to a final answer`,\n maxTurns,\n );\n}\n\n// ─── reactLoopStream(流式)──────────────────────────\n\n/**\n * 执行 ReAct 循环(流式)\n *\n * 使用 `provider.stream()` 异步迭代 chunks,yield `deltaContent` + `toolCall` + `toolResult` + `done`。\n *\n * @param input 用户输入\n * @param config 循环配置\n * @yields {ReactLoopStreamChunk} 流式 chunk\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.stream 抛错时立即传播\n */\nexport async function* reactLoopStream(\n input: string,\n config: ReactLoopConfig,\n): AsyncIterable<ReactLoopStreamChunk> {\n const messages = buildInitialMessages(input, config.systemPrompt);\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n const extras = buildRequestExtras(config);\n let totalUsage: LLMUsage | undefined;\n let turns = 0;\n\n while (turns < maxTurns) {\n turns++;\n\n let turnContent = '';\n let toolCalls: LLMToolCall[] | undefined;\n let finishReason: LLMStopReason | undefined;\n\n for await (const chunk of config.provider.stream({\n messages: [...messages],\n ...extras,\n })) {\n // 增量内容\n if (typeof chunk.deltaContent === 'string' && chunk.deltaContent.length > 0) {\n turnContent += chunk.deltaContent;\n yield { deltaContent: chunk.deltaContent };\n }\n\n // tool_calls(在最终 chunk 出现,含 id/name/arguments)\n if (chunk.toolCalls && chunk.toolCalls.length > 0) {\n toolCalls = chunk.toolCalls;\n }\n\n // finishReason + usage(在最终 chunk 出现)\n if (chunk.finishReason) {\n finishReason = chunk.finishReason;\n }\n if (chunk.usage) {\n totalUsage = accumulateUsage(totalUsage, chunk.usage);\n }\n }\n\n // 把 assistant 消息加入历史(含 toolCalls,供下一轮 LLM 上下文)\n const assistantMessage: LLMMessage = {\n role: 'assistant',\n content: turnContent,\n };\n if (toolCalls) {\n assistantMessage.toolCalls = toolCalls;\n }\n messages.push(assistantMessage);\n\n // 非 tool_calls → 循环结束\n if (finishReason !== 'tool_calls' || !toolCalls) {\n yield {\n done: {\n content: turnContent,\n turns,\n stopReason: finishReason ?? 'other',\n usage: totalUsage,\n },\n };\n return;\n }\n\n // 执行每个 tool call\n for (const toolCall of toolCalls) {\n yield { toolCall: { name: toolCall.name, arguments: toolCall.arguments } };\n\n let resultStr: string;\n try {\n const result = await config.executeTool(toolCall.name, toolCall.arguments);\n resultStr = stringifyResult(result);\n } catch (err) {\n resultStr = stringifyError(err);\n }\n\n yield { toolResult: { name: toolCall.name, result: resultStr } };\n\n messages.push({\n role: 'tool',\n content: resultStr,\n toolCallId: toolCall.id,\n });\n }\n }\n\n throw new ReactLoopError(\n `Max turns (${maxTurns}) exceeded — agent did not converge to a final answer`,\n maxTurns,\n );\n}\n","import type {\n AgentCore,\n AgentMetadata,\n AgentModule,\n AgentToolDescriptor,\n LlmConfig,\n ToolMetadata,\n ToolModule,\n} from '@faapi/faapi';\nimport type { AgentRunOptions } from './agentHandle';\nimport type { LLMProvider, LLMToolDefinition } from './provider';\nimport {\n reactLoop,\n reactLoopStream,\n type ReactLoopConfig,\n type ReactLoopResult,\n type ReactLoopStreamChunk,\n} from './reactLoop';\n\n/**\n * Agent 类——按 `agent.name` 查找元数据、组装 tool 列表、提供 `run` / `stream` / `asTool`\n *\n * 把 [reactLoop](./reactLoop.md) 与 faapi 核心的 agent/tool 注册表粘合起来:\n * - **组装 tool 列表**——合并 `resolveAgentTools`(agent 显式声明的 `tools`)+ sub-agent\n * - **执行 tool**——`reactLoop` 调 `executeTool(name, args)` 时,Agent 路由:\n * - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用\n * - `agent.` 前缀 → 递归构造 sub-agent 调用(含 `maxAgentDepth` 防护)\n * - **递归防护**——`maxAgentDepth` 限制 agent 调用 agent 的深度\n * - **自定义 run**——agent handler 导出 `run` 函数时,sub-agent 走自定义逻辑\n *\n * 详见 [agent.md](./agent.md)。\n */\n\n/** 默认最大 agent 递归深度(根 agent depth=1,sub-agent 递增) */\nconst DEFAULT_MAX_AGENT_DEPTH = 3;\n\n/**\n * 全局 agent 配置覆盖\n *\n * 来自 faapi.config.ts 的 `agent` 块,提供全局默认值。\n * agent 自身 `config.maxTurns` / `config.model` 优先于全局配置。\n */\nexport interface AgentRuntimeConfig {\n /** 默认最大对话轮数(agent 自身 maxTurns 优先) */\n maxTurns?: number;\n /** agent 调用 agent 的最大递归深度(默认 3) */\n maxAgentDepth?: number;\n}\n\n/**\n * tool schema 解析结果\n *\n * 由 Phase 3.5 的注入器实现,提供 JSON Schema(给 LLM)和校验函数(给执行前校验)。\n * - `jsonSchema` —— 发给 LLM 作为 tool 参数描述\n * - `validate` —— 执行前校验 LLM 返回的参数,失败时返回 `{ error }` 回传 LLM 重试\n */\nexport interface ToolSchemaResolution {\n /** tool 参数的 JSON Schema(发给 LLM) */\n jsonSchema: Record<string, unknown>;\n /** 执行前校验函数(成功返回 coerce 后的 value,失败返回 error) */\n validate: (\n input: Record<string, unknown>,\n ) => { ok: true; value: Record<string, unknown> } | { ok: false; error: string };\n}\n\n/**\n * Agent 运行时依赖(依赖注入)\n *\n * Agent 类**不直接 import** faapi 核心的注册表/加载器,而是通过此接口接收访问器函数。\n * 原因:\n * - **可测试**——测试传 mock 访问器,无需启动真实注册表\n * - **解耦**——Agent 类不依赖核心运行时模块\n * - **phase 边界**——Phase 3.4 实现 Agent 类逻辑,Phase 3.5 注入真实访问器\n *\n * 访问器签名与 faapi 核心对称(见 [agent.md](./agent.md) 依赖注入章节)。\n */\nexport interface AgentDeps {\n /** LLM provider 实例映射(key 是 provider 名,来自 config.agent.llms) */\n providers: Map<string, LLMProvider>;\n /** 默认 provider 实例(config.agent.defaultLlm 对应,或 llms 第一个 key) */\n defaultProvider: LLMProvider;\n /** LLM provider 配置映射(含 models,用于 options.model key 解析) */\n llms: Record<string, LlmConfig>;\n /** 默认 provider key(config.agent.defaultLlm,或 llms 第一个 key) */\n defaultLlm: string;\n /** 当前 agent 名 */\n agentName: string;\n /** 项目根目录(Phase 3.5 接线时用于加载器) */\n rootDir: string;\n /** 全局 agent 配置覆盖 */\n config?: AgentRuntimeConfig;\n /** 查 agent LLM 可见元数据(对应 agentRegistry.getAgent,返回 AgentCore) */\n getAgent: (name: string) => AgentCore | undefined;\n /** 查 agent 完整元数据(对应 agentRegistry.getAgentEntry,返回 AgentMetadata 含 filePath/hasRun) */\n getAgentEntry: (name: string) => AgentMetadata | undefined;\n /** 查 tool 元数据(对应 toolRegistry.getTool) */\n getTool: (name: string) => ToolMetadata | undefined;\n /** 解析 agent 可用常规 tool(对应 agentRegistry.resolveAgentTools) */\n resolveAgentTools: (name: string) => ToolMetadata[];\n /** 解析 agent 可调用 sub-agent 列表(对应 agentRegistry.resolveSubAgents,返回 AgentCore[]) */\n resolveSubAgents: (name: string) => AgentCore[];\n /** 动态 import tool handler(对应 loadToolModule) */\n loadToolModule: (filePath: string, functionName: string) => Promise<ToolModule>;\n /** 动态 import agent handler(对应 loadAgentModule,仅 hasRun 参数,无 hasConfig) */\n loadAgentModule: (filePath: string, hasRun: boolean) => Promise<AgentModule>;\n /** tool input 的 schema 解析(Phase 3.5 实现,可选) */\n resolveToolSchema?: (tool: ToolMetadata) => Promise<ToolSchemaResolution | undefined>;\n}\n\n/**\n * Agent 系统级错误\n *\n * agent 未注册等不可恢复错误时抛出(调用方负责捕获)。\n * sub-agent 递归超限用 {@link AgentRecursionError}。\n */\nexport class AgentError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AgentError';\n }\n}\n\n/**\n * sub-agent 递归超 `maxAgentDepth` 时抛出\n *\n * 被 [reactLoop](./reactLoop.md) catch 后错误消息回传 LLM,LLM 可据此调整策略。\n */\nexport class AgentRecursionError extends AgentError {\n /** 配置的 maxAgentDepth 值 */\n readonly maxDepth: number;\n /** 当前递归深度(超出 maxDepth) */\n readonly currentDepth: number;\n\n constructor(maxDepth: number, currentDepth: number) {\n super(\n `Agent recursion depth exceeded: current depth ${currentDepth} > maxAgentDepth ${maxDepth}`,\n );\n this.name = 'AgentRecursionError';\n this.maxDepth = maxDepth;\n this.currentDepth = currentDepth;\n }\n}\n\n/**\n * faapi Agent\n *\n * 组装 [reactLoop](./reactLoop.md) 配置、执行 tool、递归 sub-agent 的运行时入口。\n */\nexport class Agent {\n private readonly deps: AgentDeps;\n /** 当前递归深度(根 agent 为 1,sub-agent 递增) */\n private readonly depth: number;\n /**\n * tool schema 解析缓存(按 tool.name 缓存,含 undefined 结果)\n *\n * `buildToolDefinitions` 组装 LLM tool 列表时解析一次 schema(取 jsonSchema),\n * `executeTool` 执行前校验时复用同一份 schema(取 validate)——\n * 避免每次 tool 执行都重新 `loadToolSchema` + `z.toJSONSchema`。\n *\n * 实例级缓存:sub-agent 各有独立 cache(tool 集合可能不同)。\n */\n private readonly schemaCache = new Map<string, ToolSchemaResolution | undefined>();\n\n /**\n * @param deps 运行时依赖(访问器 + providers Map + defaultProvider + llms + config)\n * @param depth 递归深度(默认 1 = 根 agent;sub-agent 递归时传入 depth+1)\n */\n constructor(deps: AgentDeps, depth: number = 1) {\n this.deps = deps;\n this.depth = depth;\n }\n\n /**\n * 非流式执行——组装 config 调 [reactLoop](./reactLoop.md)\n *\n * @param input 用户输入\n * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens\n * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))\n * @returns 最终结果(content + messages + turns + stopReason + usage)\n * @throws {AgentError} agent 未注册\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.complete 抛错时立即传播\n */\n async run(input: string, options?: AgentRunOptions): Promise<ReactLoopResult> {\n const config = await this.buildLoopConfig(options);\n return reactLoop(input, config);\n }\n\n /**\n * 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)\n *\n * @param input 用户输入\n * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens\n * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))\n * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)\n * @throws {AgentError} agent 未注册\n * @throws {ReactLoopError} 超出 maxTurns\n * @throws {Error} provider.stream 抛错时立即传播\n */\n async *stream(input: string, options?: AgentRunOptions): AsyncIterable<ReactLoopStreamChunk> {\n const config = await this.buildLoopConfig(options);\n yield* reactLoopStream(input, config);\n }\n\n /**\n * 把自身包装为 `AgentToolDescriptor` 供 LLM 当 tool 调用\n *\n * 与 [agentRegistry.asTool](../../faapi/src/injection/agentRegistry.md) 同构——\n * Agent 类自带此方法便于在注入器场景直接调用(不必再过注册表)。\n *\n * @returns `AgentToolDescriptor` 或 `undefined`(agent 未注册)\n */\n asTool(): AgentToolDescriptor | undefined {\n const meta = this.deps.getAgent(this.deps.agentName);\n if (!meta) return undefined;\n return {\n kind: 'agent',\n name: `agent.${meta.name}`,\n agentName: meta.name,\n description: meta.description,\n metadata: meta,\n };\n }\n\n // ─── 内部方法 ────────────────────────────────────────\n\n /**\n * 查询 tool schema(带缓存)\n *\n * `buildToolDefinitions` 与 `executeTool` 共用此方法——\n * 首次调用触发 `deps.resolveToolSchema`(加载 zod.js + 生成 JSON Schema),\n * 后续命中缓存直接返回(含 `undefined` 结果,用 `has` 区分未解析 vs 解析为空)。\n *\n * `deps.resolveToolSchema` 未提供时直接返回 `undefined`,不写缓存。\n */\n private async getToolSchema(tool: ToolMetadata): Promise<ToolSchemaResolution | undefined> {\n if (!this.deps.resolveToolSchema) return undefined;\n if (this.schemaCache.has(tool.name)) {\n return this.schemaCache.get(tool.name);\n }\n const resolved = await this.deps.resolveToolSchema(tool);\n this.schemaCache.set(tool.name, resolved);\n return resolved;\n }\n\n /**\n * 组装 ReactLoopConfig\n *\n * 1. 查 agent 元数据(未注册抛 AgentError)——用 `getAgent` 拿 AgentCore\n * (LLM-facing 字段:systemPrompt / model / maxTurns)\n * 2. buildToolDefinitions 组装 tool 列表\n * 3. config 字段优先级(高 → 低):`options` > agent 元数据 > 全局 AgentRuntimeConfig / deps.defaultProvider\n *\n * `options.model` 是字符串 key,由 {@link resolveModelKey} 解析为 provider + model\n * (支持 llms key 精确匹配 / `provider/model` 一体化 / 纯 model 名模糊匹配)。\n * 不传 `options.model` 时用 `deps.defaultProvider` + agent 元数据 `config.model`。\n * 详见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。\n */\n private async buildLoopConfig(options?: AgentRunOptions): Promise<ReactLoopConfig> {\n const meta = this.deps.getAgent(this.deps.agentName);\n if (!meta) {\n throw new AgentError(`Agent \"${this.deps.agentName}\" is not registered`);\n }\n\n const tools = await this.buildToolDefinitions();\n\n // 解析 options.model 字符串 key → provider + model\n const { provider, model } = this.resolveModelKey(options?.model, meta);\n\n 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 executeTool: async (name, args) => this.executeTool(name, args),\n };\n }\n\n /**\n * 解析 `options.model` 字符串 key → provider + model\n *\n * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」:\n * 1. `undefined` → `deps.defaultProvider` + `meta.model`\n * 2. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key\n * 3. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model\n * (要求该 model 在 `deps.llms[provider].models` 里)\n * 4. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找\n * - 唯一 → 该 provider + 该 model\n * - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)\n * - 无 → 抛 `AgentError`\n *\n * @throws {AgentError} key 解析失败(provider/model 不存在或歧义)\n */\n private resolveModelKey(\n key: string | undefined,\n meta: AgentCore,\n ): { provider: LLMProvider; model: string | undefined } {\n // 不传 → 默认 provider + agent 元数据 model\n if (key === undefined) {\n return { provider: this.deps.defaultProvider, model: meta.model };\n }\n\n // 规则 1:精确匹配 providers key(如 'openai')\n const byProviderKey = this.deps.providers.get(key);\n if (byProviderKey) {\n const llmConfig = this.deps.llms[key];\n const firstModel = llmConfig ? Object.keys(llmConfig.models)[0] : undefined;\n return { provider: byProviderKey, model: firstModel ?? meta.model };\n }\n\n // 规则 2:含 '/' → provider/model 形式(如 'openai/gpt-4o')\n if (key.includes('/')) {\n const slashIdx = key.indexOf('/');\n const providerName = key.slice(0, slashIdx);\n const modelName = key.slice(slashIdx + 1);\n const provider = this.deps.providers.get(providerName);\n if (!provider) {\n throw new AgentError(`Unknown provider \"${providerName}\" in model key \"${key}\"`);\n }\n const llmConfig = this.deps.llms[providerName];\n if (!llmConfig || !llmConfig.models[modelName]) {\n throw new AgentError(\n `Model \"${modelName}\" not found in provider \"${providerName}\". Declare it in config.agent.llms.${providerName}.models.`,\n );\n }\n return { provider, model: modelName };\n }\n\n // 规则 3:纯 model 名模糊匹配(如 'gpt-4o')\n const matches: { provider: LLMProvider; providerName: string }[] = [];\n for (const [providerName, provider] of this.deps.providers) {\n const llmConfig = this.deps.llms[providerName];\n if (llmConfig && llmConfig.models[key]) {\n matches.push({ provider, providerName });\n }\n }\n if (matches.length === 1) {\n return { provider: matches[0].provider, model: key };\n }\n if (matches.length > 1) {\n throw new AgentError(\n `Model \"${key}\" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(', ')}). Use \"provider/model\" to disambiguate.`,\n );\n }\n throw new AgentError(\n `Model \"${key}\" not found in any provider. Declare it in config.agent.llms.*.models.`,\n );\n }\n\n /**\n * 组装 LLM 可见 tool 列表\n *\n * 合并两个来源(按 `name` 去重,先入者保留):\n * 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用\n * 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`\n *\n * 每个常规 tool 的 `input`:\n * - `resolveToolSchema` 提供 → 用其 `jsonSchema`\n * - 未提供 / tool 无 `inputTypeName` → 自由 schema `{ type: 'object' }`\n *\n * sub-agent 的 `input` 始终为 `{ type: 'object' }`(agent 参数开放)。\n */\n private async buildToolDefinitions(): Promise<LLMToolDefinition[]> {\n const definitions = new Map<string, LLMToolDefinition>();\n\n // 1. resolveAgentTools(agent 显式声明的 tools 引用)\n for (const tool of this.deps.resolveAgentTools(this.deps.agentName)) {\n if (definitions.has(tool.name)) continue;\n const schemaRes = await this.getToolSchema(tool);\n definitions.set(tool.name, {\n name: tool.name,\n description: tool.description,\n input: schemaRes?.jsonSchema ?? { type: 'object' },\n });\n }\n\n // 2. sub-agent(包装为 agent.<name>)\n for (const subAgent of this.deps.resolveSubAgents(this.deps.agentName)) {\n const name = `agent.${subAgent.name}`;\n if (definitions.has(name)) continue;\n definitions.set(name, {\n name,\n description: subAgent.description,\n input: { type: 'object' },\n });\n }\n\n return Array.from(definitions.values());\n }\n\n /**\n * tool 执行路由(由 reactLoop 调用)\n *\n * - `agent.` 前缀 → {@link executeSubAgent} 递归\n * - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用\n *\n * **常规 tool 校验失败**:不抛错,返回 `{ error }` 对象——reactLoop stringify 后\n * 作为 tool 结果回传 LLM,LLM 可据此修正参数重试。\n *\n * **tool 未找到 / 加载失败**:抛错,被 reactLoop catch 后同样回传 LLM。\n */\n private async executeTool(name: string, args: Record<string, unknown>): Promise<unknown> {\n // sub-agent 递归\n if (name.startsWith('agent.')) {\n return this.executeSubAgent(name.slice(6), args);\n }\n\n // 常规 tool\n const tool = this.deps.getTool(name);\n if (!tool) {\n throw new Error(`Tool \"${name}\" not found`);\n }\n\n // 可选 input 校验\n const schemaRes = await this.getToolSchema(tool);\n let callArgs = args;\n if (schemaRes) {\n const result = schemaRes.validate(args);\n if (!result.ok) {\n // 校验失败:返回 { error } 对象,不调用 handler——错误回传 LLM 重试\n return { error: result.error };\n }\n callArgs = result.value ?? args;\n }\n\n const mod = await this.deps.loadToolModule(tool.filePath, tool.functionName);\n return await mod.handler(callArgs);\n }\n\n /**\n * sub-agent 递归执行\n *\n * 1. `maxAgentDepth` 防护——超限抛 {@link AgentRecursionError}\n * 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`\n * 3. 无 `run` 时调 `subAgent.run(JSON.stringify(args))` 走默认 reactLoop\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(subName: string, args: Record<string, unknown>): Promise<unknown> {\n const newDepth = this.depth + 1;\n const maxDepth = this.deps.config?.maxAgentDepth ?? DEFAULT_MAX_AGENT_DEPTH;\n if (newDepth > maxDepth) {\n throw new AgentRecursionError(maxDepth, newDepth);\n }\n\n // 构造子 agent(复用父 deps,仅覆盖 agentName)\n const subDeps: AgentDeps = { ...this.deps, agentName: subName };\n const subAgent = new Agent(subDeps, newDepth);\n\n // 自定义 run:sub-agent handler 导出 run 函数时走自定义逻辑\n // 用 getAgentEntry 拿 AgentMetadata(含 filePath/hasRun),DB skill 无文件走默认 reactLoop\n const entry = this.deps.getAgentEntry(subName);\n if (entry?.hasRun) {\n const mod = await this.deps.loadAgentModule(entry.filePath, entry.hasRun);\n if (mod.run) {\n return await mod.run(args);\n }\n }\n\n // 默认 reactLoop:stringify args 作为 user 消息\n const result = await subAgent.run(typeof args === 'string' ? args : JSON.stringify(args));\n return result.content;\n }\n}\n","/**\n * @faapi/agent faapi 插件——注册 agent handle 工厂,让 handler 的 `agent` 参数注入可用的 Agent 实例\n *\n * 在 faapi.config.ts 中声明:\n * ```ts\n * import type { FaapiConfig } from '@faapi/faapi';\n *\n * export default {\n * agent: {\n * llms: {\n * openai: {\n * provider: 'openai',\n * apiKey: process.env.OPENAI_API_KEY,\n * baseURL: 'https://api.openai.com/v1',\n * models: { 'gpt-4o': {}, 'gpt-4o-mini': { temperature: 0.5 } },\n * },\n * },\n * defaultLlm: 'openai',\n * defaultAgent: 'researcher',\n * maxTurns: 10,\n * },\n * plugins: ['@faapi/agent'],\n * } satisfies FaapiConfig;\n * ```\n *\n * 插件 setup 时:\n * 1. 遍历 `config.agent.llms` → 每项调 `createProvider` → `Map<providerKey, LLMProvider>`\n * 2. 读 `config.agent.defaultLlm` → `defaultProvider`(未设时用 `llms` 第一个 key)\n * 3. 读 `config.agent.defaultAgent` / `maxTurns` / `maxAgentDepth`\n * 4. 从 `@faapi/faapi` import 注册表/加载器访问器(getAgent / getTool / resolveAgentTools /\n * resolveSubAgents / loadAgentModule / loadToolModule)\n * 5. `registerAgentHandleFactory` 注册工厂——每次请求时构造 [Agent](./agent.md) 实例注入到\n * handler 的 `agent` 参数\n *\n * 配置缺失时(`agent.llms` 或 `agent.defaultAgent` 未设置)跳过工厂注册并打印警告,\n * handler 的 `agent` 参数注入 `undefined`。\n *\n * 详见 [plugin.md](./plugin.md)。\n */\n\nimport {\n registerAgentHandleFactory,\n getAgent,\n getAgentEntry,\n getTool,\n resolveAgentTools,\n resolveSubAgents,\n loadAgentModule,\n loadToolModule,\n loadToolSchema,\n type FaapiPlugin,\n type PluginContext,\n type AgentConfig,\n type ToolMetadata,\n} from '@faapi/faapi';\nimport { z } from 'zod';\nimport { Agent, type AgentRuntimeConfig, type ToolSchemaResolution } from './agent';\nimport type { LLMProvider } from './provider';\nimport { createProvider } from './provider';\n\n/**\n * 加载 tool 的 zod.js → 生成 JSON Schema + 校验函数\n *\n * 模块级函数(非 setup 内闭包)——setup 时用 `rootDir` 偏函数绑定一次,\n * 工厂内直接复用,避免每次请求重建闭包。\n *\n * 详见 [plugin.md](./plugin.md) 的 resolveToolSchema 实现。\n *\n * @param tool tool 元数据(含 filePath / inputTypeName)\n * @param rootDir 项目根目录(用于 dev 按需编译模式)\n * @returns `ToolSchemaResolution` 或 `undefined`(zod.js 不存在 / tool 无 inputTypeName)\n */\nasync function resolveToolSchemaImpl(\n tool: ToolMetadata,\n rootDir: string,\n): Promise<ToolSchemaResolution | undefined> {\n const schemaMod = await loadToolSchema(tool, rootDir);\n if (!schemaMod) return undefined;\n const schema = schemaMod.schema as z.ZodType;\n return {\n jsonSchema: z.toJSONSchema(schema),\n validate: (input) => {\n const result = schema.safeParse(input);\n if (result.success) {\n return { ok: true as const, value: result.data as Record<string, unknown> };\n }\n return { ok: false as const, error: result.error.message };\n },\n } satisfies ToolSchemaResolution;\n}\n\n/**\n * 从 PluginContext.config 读取 agent 配置\n *\n * `agent` 不是 FAAPI_CONFIG_KEYS 的成员(它是 agent 子系统配置,非核心配置),\n * 因此会随自定义业务配置一起传到 `ctx.config`。\n */\nfunction readAgentConfig(ctx: PluginContext): AgentConfig | undefined {\n const raw = ctx.config?.agent;\n if (raw === undefined || raw === null) return undefined;\n return raw as AgentConfig;\n}\n\n/**\n * @faapi/agent faapi 插件入口\n *\n * 在 faapi.config.ts 的 `plugins` 字段中声明 `'@faapi/agent'` 即可启用。\n * 插件加载后,handler 的 `agent` 参数可注入可用的 [AgentHandle](./agentHandle.md)。\n */\nconst agentPlugin: FaapiPlugin = {\n name: '@faapi/agent',\n setup(ctx: PluginContext): void {\n const agentConfig = readAgentConfig(ctx);\n\n // 必要配置检查——缺失时跳过工厂注册,agent 参数注入 undefined\n if (!agentConfig?.llms) {\n console.warn(\n '! @faapi/agent: config.agent.llms not configured, agent parameter injection disabled',\n );\n return;\n }\n if (!agentConfig.defaultAgent) {\n console.warn(\n '! @faapi/agent: config.agent.defaultAgent not configured, agent parameter injection disabled',\n );\n return;\n }\n\n // 创建 LLM provider 实例 Map(key 是 provider 名,来自 config.agent.llms)\n // 单例,所有请求共享;每个 provider 实例对应一个 LlmConfig\n const llms = agentConfig.llms;\n const providers = new Map<string, LLMProvider>();\n for (const [name, llmConfig] of Object.entries(llms)) {\n providers.set(name, createProvider(llmConfig));\n }\n\n // 默认 provider:config.agent.defaultLlm 优先,否则取 llms 第一个 key\n const defaultLlm = agentConfig.defaultLlm ?? Object.keys(llms)[0];\n const defaultProvider = providers.get(defaultLlm);\n if (!defaultProvider) {\n console.warn(\n `! @faapi/agent: config.agent.defaultLlm \"${defaultLlm}\" not found in llms, agent parameter injection disabled`,\n );\n return;\n }\n\n // 全局 agent 运行时配置覆盖\n const runtimeConfig: AgentRuntimeConfig = {\n maxTurns: agentConfig.maxTurns,\n maxAgentDepth: agentConfig.maxAgentDepth,\n };\n\n const rootDir = ctx.rootDir;\n const defaultAgent = agentConfig.defaultAgent;\n // resolveToolSchema 偏函数(setup 内创建一次,工厂内复用,避免每次请求重建闭包)\n const resolveToolSchema = (tool: ToolMetadata) => resolveToolSchemaImpl(tool, rootDir);\n\n // 注册 agent handle 工厂——每次请求时构造 Agent 实例\n // Agent 构造轻量(仅存 deps),实际 LLM 调用在 run/stream 时才发生\n registerAgentHandleFactory(() => {\n return new Agent({\n providers,\n defaultProvider,\n llms,\n defaultLlm,\n agentName: defaultAgent,\n rootDir,\n config: runtimeConfig,\n // 注册表/加载器访问器——从 @faapi/faapi import 的单例模块\n // createAppBase 启动时已水合 agentRegistry / toolRegistry\n // getAgent 返回 AgentCore(LLM-facing);getAgentEntry 返回 AgentMetadata(含 filePath/hasRun,供加载 handler.js)\n getAgent,\n getAgentEntry,\n getTool,\n resolveAgentTools,\n resolveSubAgents,\n // 加载器包装:注入 rootDir 用于 dev 按需编译模式\n loadToolModule: (filePath, functionName) => loadToolModule(filePath, functionName, rootDir),\n loadAgentModule: (filePath, hasRun) => loadAgentModule(filePath, hasRun, rootDir),\n // tool schema 解析(zod.js → JSON Schema + safeParse 校验)\n resolveToolSchema,\n });\n });\n\n console.log(\n `- @faapi/agent: default agent \"${defaultAgent}\" (provider: ${defaultLlm}) available via agent parameter injection`,\n );\n },\n};\n\nexport default agentPlugin;\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faapi/agent",
3
- "version": "3.0.0",
3
+ "version": "3.1.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": "^3.1.0",
27
27
  "zod": "^4.4.3",
28
- "@faapi/faapi": "3.0.0"
28
+ "@faapi/faapi": "3.1.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "typescript": ">=5.7.0",
32
32
  "zod": "^4.4.3",
33
- "@faapi/faapi": "^3.0.0"
33
+ "@faapi/faapi": "^3.1.0"
34
34
  },
35
35
  "peerDependenciesMeta": {
36
36
  "typescript": {