@faapi/agent 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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() {
@@ -299,6 +317,11 @@ function createProvider(config) {
299
317
  }
300
318
  }
301
319
 
320
+ // src/trace.ts
321
+ function isTracingToolResult(value) {
322
+ return typeof value === "object" && value !== null && value.__trace === true;
323
+ }
324
+
302
325
  // src/reactLoop.ts
303
326
  var ReactLoopError = class extends Error {
304
327
  /** 配置的 maxTurns 值 */
@@ -343,14 +366,29 @@ function buildRequestExtras(config) {
343
366
  maxTokens: config.maxTokens
344
367
  };
345
368
  }
369
+ function nowMs() {
370
+ return performance.now();
371
+ }
372
+ function extractSubAgentName(toolName) {
373
+ const prefix = "agent.";
374
+ if (toolName.startsWith(prefix)) {
375
+ return toolName.slice(prefix.length);
376
+ }
377
+ return toolName;
378
+ }
346
379
  async function reactLoop(input, config) {
380
+ const enableTracing = config.enableTracing ?? true;
347
381
  const messages = buildInitialMessages(input, config.systemPrompt);
348
382
  const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
349
383
  const extras = buildRequestExtras(config);
350
384
  let totalUsage;
351
385
  let turns = 0;
386
+ const traceStartedAt = enableTracing ? nowMs() : 0;
387
+ const traceEvents = enableTracing ? [] : void 0;
352
388
  while (turns < maxTurns) {
353
389
  turns++;
390
+ const llmStartedAt = enableTracing ? nowMs() : 0;
391
+ const inputSnapshot = enableTracing ? [...messages] : void 0;
354
392
  const response = await config.provider.complete({
355
393
  messages: [...messages],
356
394
  ...extras
@@ -358,23 +396,87 @@ async function reactLoop(input, config) {
358
396
  if (response.usage) {
359
397
  totalUsage = accumulateUsage(totalUsage, response.usage);
360
398
  }
399
+ if (enableTracing) {
400
+ const llmEndedAt = nowMs();
401
+ traceEvents.push({
402
+ type: "llm_call",
403
+ turn: turns,
404
+ startedAt: llmStartedAt,
405
+ durationMs: llmEndedAt - llmStartedAt,
406
+ model: config.model ?? "",
407
+ inputMessages: inputSnapshot,
408
+ response: response.message,
409
+ stopReason: response.stopReason,
410
+ usage: response.usage
411
+ });
412
+ }
361
413
  messages.push(response.message);
362
414
  if (response.stopReason !== "tool_calls" || !response.message.toolCalls) {
415
+ const traceEndedAt = enableTracing ? nowMs() : 0;
363
416
  return {
364
417
  content: response.message.content,
365
418
  messages,
366
419
  turns,
367
420
  stopReason: response.stopReason,
368
- usage: totalUsage
421
+ usage: totalUsage,
422
+ trace: enableTracing ? {
423
+ agentName: "",
424
+ startedAt: traceStartedAt,
425
+ durationMs: traceEndedAt - traceStartedAt,
426
+ turns,
427
+ usage: totalUsage,
428
+ stopReason: response.stopReason,
429
+ content: response.message.content,
430
+ events: traceEvents
431
+ } : void 0
369
432
  };
370
433
  }
371
434
  for (const toolCall of response.message.toolCalls) {
435
+ const toolStartedAt = enableTracing ? nowMs() : 0;
372
436
  let resultStr;
437
+ let rawResult;
438
+ let toolErr;
439
+ let hasError = false;
373
440
  try {
374
- const result = await config.executeTool(toolCall.name, toolCall.arguments);
375
- resultStr = stringifyResult(result);
441
+ rawResult = await config.executeTool(toolCall.name, toolCall.arguments);
442
+ if (isTracingToolResult(rawResult)) {
443
+ resultStr = stringifyResult(rawResult.result);
444
+ } else {
445
+ resultStr = stringifyResult(rawResult);
446
+ }
376
447
  } catch (err) {
448
+ hasError = true;
449
+ toolErr = err;
377
450
  resultStr = stringifyError(err);
451
+ rawResult = void 0;
452
+ }
453
+ if (enableTracing) {
454
+ const toolEndedAt = nowMs();
455
+ if (isTracingToolResult(rawResult)) {
456
+ traceEvents.push({
457
+ type: "subagent_call",
458
+ turn: turns,
459
+ startedAt: toolStartedAt,
460
+ durationMs: toolEndedAt - toolStartedAt,
461
+ toolCallId: toolCall.id,
462
+ agentName: extractSubAgentName(toolCall.name),
463
+ input: JSON.stringify(toolCall.arguments),
464
+ trace: rawResult.trace,
465
+ result: resultStr
466
+ });
467
+ } else {
468
+ traceEvents.push({
469
+ type: "tool_call",
470
+ turn: turns,
471
+ startedAt: toolStartedAt,
472
+ durationMs: toolEndedAt - toolStartedAt,
473
+ toolCallId: toolCall.id,
474
+ name: toolCall.name,
475
+ arguments: toolCall.arguments,
476
+ result: resultStr,
477
+ error: hasError ? stringifyError(toolErr) : void 0
478
+ });
479
+ }
378
480
  }
379
481
  messages.push({
380
482
  role: "tool",
@@ -389,6 +491,7 @@ async function reactLoop(input, config) {
389
491
  );
390
492
  }
391
493
  async function* reactLoopStream(input, config) {
494
+ const enableTracing = config.enableTracing ?? true;
392
495
  const messages = buildInitialMessages(input, config.systemPrompt);
393
496
  const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
394
497
  const extras = buildRequestExtras(config);
@@ -396,9 +499,12 @@ async function* reactLoopStream(input, config) {
396
499
  let turns = 0;
397
500
  while (turns < maxTurns) {
398
501
  turns++;
502
+ const llmStartedAt = enableTracing ? nowMs() : 0;
503
+ const inputSnapshot = enableTracing ? [...messages] : void 0;
399
504
  let turnContent = "";
400
505
  let toolCalls;
401
506
  let finishReason;
507
+ let turnUsage;
402
508
  for await (const chunk of config.provider.stream({
403
509
  messages: [...messages],
404
510
  ...extras
@@ -415,6 +521,7 @@ async function* reactLoopStream(input, config) {
415
521
  }
416
522
  if (chunk.usage) {
417
523
  totalUsage = accumulateUsage(totalUsage, chunk.usage);
524
+ turnUsage = chunk.usage;
418
525
  }
419
526
  }
420
527
  const assistantMessage = {
@@ -425,6 +532,22 @@ async function* reactLoopStream(input, config) {
425
532
  assistantMessage.toolCalls = toolCalls;
426
533
  }
427
534
  messages.push(assistantMessage);
535
+ if (enableTracing) {
536
+ const llmEndedAt = nowMs();
537
+ yield {
538
+ traceEvent: {
539
+ type: "llm_call",
540
+ turn: turns,
541
+ startedAt: llmStartedAt,
542
+ durationMs: llmEndedAt - llmStartedAt,
543
+ model: config.model ?? "",
544
+ inputMessages: inputSnapshot,
545
+ response: assistantMessage,
546
+ stopReason: finishReason ?? "other",
547
+ usage: turnUsage
548
+ }
549
+ };
550
+ }
428
551
  if (finishReason !== "tool_calls" || !toolCalls) {
429
552
  yield {
430
553
  done: {
@@ -438,14 +561,57 @@ async function* reactLoopStream(input, config) {
438
561
  }
439
562
  for (const toolCall of toolCalls) {
440
563
  yield { toolCall: { name: toolCall.name, arguments: toolCall.arguments } };
564
+ const toolStartedAt = enableTracing ? nowMs() : 0;
441
565
  let resultStr;
566
+ let rawResult;
567
+ let toolErr;
568
+ let hasError = false;
442
569
  try {
443
- const result = await config.executeTool(toolCall.name, toolCall.arguments);
444
- resultStr = stringifyResult(result);
570
+ rawResult = await config.executeTool(toolCall.name, toolCall.arguments);
571
+ if (isTracingToolResult(rawResult)) {
572
+ resultStr = stringifyResult(rawResult.result);
573
+ } else {
574
+ resultStr = stringifyResult(rawResult);
575
+ }
445
576
  } catch (err) {
577
+ hasError = true;
578
+ toolErr = err;
446
579
  resultStr = stringifyError(err);
580
+ rawResult = void 0;
447
581
  }
448
582
  yield { toolResult: { name: toolCall.name, result: resultStr } };
583
+ if (enableTracing) {
584
+ const toolEndedAt = nowMs();
585
+ if (isTracingToolResult(rawResult)) {
586
+ yield {
587
+ traceEvent: {
588
+ type: "subagent_call",
589
+ turn: turns,
590
+ startedAt: toolStartedAt,
591
+ durationMs: toolEndedAt - toolStartedAt,
592
+ toolCallId: toolCall.id,
593
+ agentName: extractSubAgentName(toolCall.name),
594
+ input: JSON.stringify(toolCall.arguments),
595
+ trace: rawResult.trace,
596
+ result: resultStr
597
+ }
598
+ };
599
+ } else {
600
+ yield {
601
+ traceEvent: {
602
+ type: "tool_call",
603
+ turn: turns,
604
+ startedAt: toolStartedAt,
605
+ durationMs: toolEndedAt - toolStartedAt,
606
+ toolCallId: toolCall.id,
607
+ name: toolCall.name,
608
+ arguments: toolCall.arguments,
609
+ result: resultStr,
610
+ error: hasError ? stringifyError(toolErr) : void 0
611
+ }
612
+ };
613
+ }
614
+ }
449
615
  messages.push({
450
616
  role: "tool",
451
617
  content: resultStr,
@@ -496,7 +662,7 @@ var Agent = class _Agent {
496
662
  */
497
663
  schemaCache = /* @__PURE__ */ new Map();
498
664
  /**
499
- * @param deps 运行时依赖(访问器 + provider + config)
665
+ * @param deps 运行时依赖(访问器 + providers Map + defaultProvider + llms + config)
500
666
  * @param depth 递归深度(默认 1 = 根 agent;sub-agent 递归时传入 depth+1)
501
667
  */
502
668
  constructor(deps, depth = 1) {
@@ -506,27 +672,38 @@ var Agent = class _Agent {
506
672
  /**
507
673
  * 非流式执行——组装 config 调 [reactLoop](./reactLoop.md)
508
674
  *
675
+ * reactLoop 不知 agent 名(只关心循环逻辑),返回的 `result.trace.agentName` 为空字符串。
676
+ * 本方法在 reactLoop 返回后填充 `this.deps.agentName`,让顶层 trace 标识"是哪个 agent 跑的"。
677
+ *
509
678
  * @param input 用户输入
510
- * @returns 最终结果(content + messages + turns + stopReason + usage)
679
+ * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens / enableTracing
680
+ * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))
681
+ * @returns 最终结果(content + messages + turns + stopReason + usage + trace?)
511
682
  * @throws {AgentError} agent 未注册
512
683
  * @throws {ReactLoopError} 超出 maxTurns
513
684
  * @throws {Error} provider.complete 抛错时立即传播
514
685
  */
515
- async run(input) {
516
- const config = await this.buildLoopConfig();
517
- return reactLoop(input, config);
686
+ async run(input, options) {
687
+ const config = await this.buildLoopConfig(options);
688
+ const result = await reactLoop(input, config);
689
+ if (result.trace) {
690
+ result.trace.agentName = this.deps.agentName;
691
+ }
692
+ return result;
518
693
  }
519
694
  /**
520
695
  * 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)
521
696
  *
522
697
  * @param input 用户输入
698
+ * @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens
699
+ * (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))
523
700
  * @yields 流式 chunk(deltaContent / toolCall / toolResult / done)
524
701
  * @throws {AgentError} agent 未注册
525
702
  * @throws {ReactLoopError} 超出 maxTurns
526
703
  * @throws {Error} provider.stream 抛错时立即传播
527
704
  */
528
- async *stream(input) {
529
- const config = await this.buildLoopConfig();
705
+ async *stream(input, options) {
706
+ const config = await this.buildLoopConfig(options);
530
707
  yield* reactLoopStream(input, config);
531
708
  }
532
709
  /**
@@ -570,32 +747,102 @@ var Agent = class _Agent {
570
747
  /**
571
748
  * 组装 ReactLoopConfig
572
749
  *
573
- * 1. 查 agent 元数据(未注册抛 AgentError
750
+ * 1. 查 agent 元数据(未注册抛 AgentError)——用 `getAgent` 拿 AgentCore
751
+ * (LLM-facing 字段:systemPrompt / model / maxTurns)
574
752
  * 2. buildToolDefinitions 组装 tool 列表
575
- * 3. config 字段优先级:agent 元数据 > 全局 AgentRuntimeConfig
753
+ * 3. config 字段优先级(高 → 低):`options` > agent 元数据 > 全局 AgentRuntimeConfig / deps.defaultProvider
754
+ *
755
+ * `options.model` 是字符串 key,由 {@link resolveModelKey} 解析为 provider + model
756
+ * (支持 llms key 精确匹配 / `provider/model` 一体化 / 纯 model 名模糊匹配)。
757
+ * 不传 `options.model` 时用 `deps.defaultProvider` + agent 元数据 `config.model`。
758
+ * 详见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。
576
759
  */
577
- async buildLoopConfig() {
760
+ async buildLoopConfig(options) {
578
761
  const meta = this.deps.getAgent(this.deps.agentName);
579
762
  if (!meta) {
580
763
  throw new AgentError(`Agent "${this.deps.agentName}" is not registered`);
581
764
  }
582
765
  const tools = await this.buildToolDefinitions();
766
+ const { provider, model } = this.resolveModelKey(options?.model, meta);
767
+ const enableTracing = options?.enableTracing ?? this.deps.config?.enableTracing ?? true;
583
768
  return {
584
- provider: this.deps.provider,
769
+ provider,
585
770
  systemPrompt: meta.systemPrompt,
586
- model: meta.model,
771
+ model,
772
+ temperature: options?.temperature,
773
+ maxTokens: options?.maxTokens,
587
774
  maxTurns: meta.maxTurns ?? this.deps.config?.maxTurns,
588
775
  tools,
589
- executeTool: async (name, args) => this.executeTool(name, args)
776
+ enableTracing,
777
+ executeTool: async (name, args) => this.executeTool(name, args, enableTracing)
590
778
  };
591
779
  }
780
+ /**
781
+ * 解析 `options.model` 字符串 key → provider + model
782
+ *
783
+ * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」:
784
+ * 1. `undefined` → `deps.defaultProvider` + `meta.model`
785
+ * 2. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key
786
+ * 3. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model
787
+ * (要求该 model 在 `deps.llms[provider].models` 里)
788
+ * 4. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找
789
+ * - 唯一 → 该 provider + 该 model
790
+ * - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)
791
+ * - 无 → 抛 `AgentError`
792
+ *
793
+ * @throws {AgentError} key 解析失败(provider/model 不存在或歧义)
794
+ */
795
+ resolveModelKey(key, meta) {
796
+ if (key === void 0) {
797
+ return { provider: this.deps.defaultProvider, model: meta.model };
798
+ }
799
+ const byProviderKey = this.deps.providers.get(key);
800
+ if (byProviderKey) {
801
+ const llmConfig = this.deps.llms[key];
802
+ const firstModel = llmConfig ? Object.keys(llmConfig.models)[0] : void 0;
803
+ return { provider: byProviderKey, model: firstModel ?? meta.model };
804
+ }
805
+ if (key.includes("/")) {
806
+ const slashIdx = key.indexOf("/");
807
+ const providerName = key.slice(0, slashIdx);
808
+ const modelName = key.slice(slashIdx + 1);
809
+ const provider = this.deps.providers.get(providerName);
810
+ if (!provider) {
811
+ throw new AgentError(`Unknown provider "${providerName}" in model key "${key}"`);
812
+ }
813
+ const llmConfig = this.deps.llms[providerName];
814
+ if (!llmConfig || !llmConfig.models[modelName]) {
815
+ throw new AgentError(
816
+ `Model "${modelName}" not found in provider "${providerName}". Declare it in config.agent.llms.${providerName}.models.`
817
+ );
818
+ }
819
+ return { provider, model: modelName };
820
+ }
821
+ const matches = [];
822
+ for (const [providerName, provider] of this.deps.providers) {
823
+ const llmConfig = this.deps.llms[providerName];
824
+ if (llmConfig && llmConfig.models[key]) {
825
+ matches.push({ provider, providerName });
826
+ }
827
+ }
828
+ if (matches.length === 1) {
829
+ return { provider: matches[0].provider, model: key };
830
+ }
831
+ if (matches.length > 1) {
832
+ throw new AgentError(
833
+ `Model "${key}" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(", ")}). Use "provider/model" to disambiguate.`
834
+ );
835
+ }
836
+ throw new AgentError(
837
+ `Model "${key}" not found in any provider. Declare it in config.agent.llms.*.models.`
838
+ );
839
+ }
592
840
  /**
593
841
  * 组装 LLM 可见 tool 列表
594
842
  *
595
- * 合并三个来源(按 `name` 去重,先入者保留):
843
+ * 合并两个来源(按 `name` 去重,先入者保留):
596
844
  * 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用
597
- * 2. **全局 defaultTools** —— `config.defaultTools` 中的 tool 名(所有 agent 共享)
598
- * 3. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`
845
+ * 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`
599
846
  *
600
847
  * 每个常规 tool 的 `input`:
601
848
  * - `resolveToolSchema` 提供 → 用其 `jsonSchema`
@@ -614,20 +861,6 @@ var Agent = class _Agent {
614
861
  input: schemaRes?.jsonSchema ?? { type: "object" }
615
862
  });
616
863
  }
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
864
  for (const subAgent of this.deps.resolveSubAgents(this.deps.agentName)) {
632
865
  const name = `agent.${subAgent.name}`;
633
866
  if (definitions.has(name)) continue;
@@ -642,17 +875,20 @@ var Agent = class _Agent {
642
875
  /**
643
876
  * tool 执行路由(由 reactLoop 调用)
644
877
  *
645
- * - `agent.` 前缀 → {@link executeSubAgent} 递归
878
+ * - `agent.` 前缀 → {@link executeSubAgent} 递归(含 enableTracing + TracingToolResult 包装)
646
879
  * - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用
647
880
  *
881
+ * `enableTracing` 由 [buildLoopConfig](#buildLoopConfig) 闭包捕获传入,用于 sub-agent
882
+ * 调用时决定是否包装 [TracingToolResult](./trace.md) 携带 sub-trace。
883
+ *
648
884
  * **常规 tool 校验失败**:不抛错,返回 `{ error }` 对象——reactLoop stringify 后
649
885
  * 作为 tool 结果回传 LLM,LLM 可据此修正参数重试。
650
886
  *
651
887
  * **tool 未找到 / 加载失败**:抛错,被 reactLoop catch 后同样回传 LLM。
652
888
  */
653
- async executeTool(name, args) {
889
+ async executeTool(name, args, enableTracing) {
654
890
  if (name.startsWith("agent.")) {
655
- return this.executeSubAgent(name.slice(6), args);
891
+ return this.executeSubAgent(name.slice(6), args, enableTracing);
656
892
  }
657
893
  const tool = this.deps.getTool(name);
658
894
  if (!tool) {
@@ -674,13 +910,26 @@ var Agent = class _Agent {
674
910
  * sub-agent 递归执行
675
911
  *
676
912
  * 1. `maxAgentDepth` 防护——超限抛 {@link AgentRecursionError}
677
- * 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`
678
- * 3. 无 `run` 时调 `subAgent.run(JSON.stringify(args))` 走默认 reactLoop
913
+ * 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`(无 trace,与常规 tool 一致)
914
+ * 3. 无 `run` 时调 `subAgent.run(stringify(args), { enableTracing })` 走默认 reactLoop
915
+ *
916
+ * **tracing 路径**:`enableTracing=true` 时,subAgent.run 返回的 `result.trace`(agentName
917
+ * 已被 `Agent.run` 填为 subName)被包装为 [TracingToolResult](./trace.md) 返回给 reactLoop。
918
+ * reactLoop 通过 `isTracingToolResult` 识别后发出 `subagent_call` 事件,嵌入 sub-trace
919
+ * (递归结构,业务方可还原完整调用树)。`enableTracing=false` 时返回 `result.content`
920
+ * (unknown,与常规 tool 一致,零开销)。
921
+ *
922
+ * **自定义 run 无 trace**:业务方导出 `run` 函数时直接返回业务结果,无法采集 sub-agent
923
+ * 内部明细——需 trace 时应让 sub-agent 走默认 reactLoop(不导出 `run`)。
679
924
  *
680
925
  * 自定义 run 接收原始 args 对象;默认 reactLoop 接收 stringify 后的 args
681
926
  * 作为 user 消息(agent-as-tool input 为开放式 JSON)。
927
+ *
928
+ * 加载 handler.js 用 `getAgentEntry`(返回 AgentMetadata,含 filePath/hasRun),
929
+ * 而非 `getAgent`(返回 AgentCore,无代码加载细节)。DB skill 无文件,
930
+ * `getAgentEntry` 返回 `undefined`,走默认 reactLoop。
682
931
  */
683
- async executeSubAgent(subName, args) {
932
+ async executeSubAgent(subName, args, enableTracing) {
684
933
  const newDepth = this.depth + 1;
685
934
  const maxDepth = this.deps.config?.maxAgentDepth ?? DEFAULT_MAX_AGENT_DEPTH;
686
935
  if (newDepth > maxDepth) {
@@ -688,14 +937,23 @@ var Agent = class _Agent {
688
937
  }
689
938
  const subDeps = { ...this.deps, agentName: subName };
690
939
  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);
940
+ const entry = this.deps.getAgentEntry(subName);
941
+ if (entry?.hasRun) {
942
+ const mod = await this.deps.loadAgentModule(entry.filePath, entry.hasRun);
694
943
  if (mod.run) {
695
944
  return await mod.run(args);
696
945
  }
697
946
  }
698
- const result = await subAgent.run(typeof args === "string" ? args : JSON.stringify(args));
947
+ const result = await subAgent.run(typeof args === "string" ? args : JSON.stringify(args), {
948
+ enableTracing
949
+ });
950
+ if (enableTracing && result.trace) {
951
+ return {
952
+ __trace: true,
953
+ result: result.content,
954
+ trace: result.trace
955
+ };
956
+ }
699
957
  return result.content;
700
958
  }
701
959
  };
@@ -704,6 +962,7 @@ var Agent = class _Agent {
704
962
  import {
705
963
  registerAgentHandleFactory,
706
964
  getAgent,
965
+ getAgentEntry,
707
966
  getTool,
708
967
  resolveAgentTools,
709
968
  resolveSubAgents,
@@ -736,9 +995,9 @@ var agentPlugin = {
736
995
  name: "@faapi/agent",
737
996
  setup(ctx) {
738
997
  const agentConfig = readAgentConfig(ctx);
739
- if (!agentConfig?.llm) {
998
+ if (!agentConfig?.llms) {
740
999
  console.warn(
741
- "! @faapi/agent: config.agent.llm not configured, agent parameter injection disabled"
1000
+ "! @faapi/agent: config.agent.llms not configured, agent parameter injection disabled"
742
1001
  );
743
1002
  return;
744
1003
  }
@@ -748,36 +1007,52 @@ var agentPlugin = {
748
1007
  );
749
1008
  return;
750
1009
  }
751
- const provider = createProvider(agentConfig.llm);
1010
+ const llms = agentConfig.llms;
1011
+ const providers = /* @__PURE__ */ new Map();
1012
+ for (const [name, llmConfig] of Object.entries(llms)) {
1013
+ providers.set(name, createProvider(llmConfig));
1014
+ }
1015
+ const defaultLlm = agentConfig.defaultLlm ?? Object.keys(llms)[0];
1016
+ const defaultProvider = providers.get(defaultLlm);
1017
+ if (!defaultProvider) {
1018
+ console.warn(
1019
+ `! @faapi/agent: config.agent.defaultLlm "${defaultLlm}" not found in llms, agent parameter injection disabled`
1020
+ );
1021
+ return;
1022
+ }
752
1023
  const runtimeConfig = {
753
1024
  maxTurns: agentConfig.maxTurns,
754
- maxAgentDepth: agentConfig.maxAgentDepth,
755
- defaultTools: agentConfig.defaultTools
1025
+ maxAgentDepth: agentConfig.maxAgentDepth
756
1026
  };
757
1027
  const rootDir = ctx.rootDir;
758
1028
  const defaultAgent = agentConfig.defaultAgent;
759
1029
  const resolveToolSchema = (tool) => resolveToolSchemaImpl(tool, rootDir);
760
1030
  registerAgentHandleFactory(() => {
761
1031
  return new Agent({
762
- provider,
1032
+ providers,
1033
+ defaultProvider,
1034
+ llms,
1035
+ defaultLlm,
763
1036
  agentName: defaultAgent,
764
1037
  rootDir,
765
1038
  config: runtimeConfig,
766
1039
  // 注册表/加载器访问器——从 @faapi/faapi import 的单例模块
767
1040
  // createAppBase 启动时已水合 agentRegistry / toolRegistry
1041
+ // getAgent 返回 AgentCore(LLM-facing);getAgentEntry 返回 AgentMetadata(含 filePath/hasRun,供加载 handler.js)
768
1042
  getAgent,
1043
+ getAgentEntry,
769
1044
  getTool,
770
1045
  resolveAgentTools,
771
1046
  resolveSubAgents,
772
1047
  // 加载器包装:注入 rootDir 用于 dev 按需编译模式
773
1048
  loadToolModule: (filePath, functionName) => loadToolModule(filePath, functionName, rootDir),
774
- loadAgentModule: (filePath, hasConfig, hasRun) => loadAgentModule(filePath, hasConfig, hasRun, rootDir),
1049
+ loadAgentModule: (filePath, hasRun) => loadAgentModule(filePath, hasRun, rootDir),
775
1050
  // tool schema 解析(zod.js → JSON Schema + safeParse 校验)
776
1051
  resolveToolSchema
777
1052
  });
778
1053
  });
779
1054
  console.log(
780
- `- @faapi/agent: default agent "${defaultAgent}" available via agent parameter injection`
1055
+ `- @faapi/agent: default agent "${defaultAgent}" (provider: ${defaultLlm}) available via agent parameter injection`
781
1056
  );
782
1057
  }
783
1058
  };
@@ -791,6 +1066,7 @@ export {
791
1066
  createOpenAIProvider,
792
1067
  createProvider,
793
1068
  plugin_default as default,
1069
+ isTracingToolResult,
794
1070
  reactLoop,
795
1071
  reactLoopStream
796
1072
  };