@faapi/agent 6.16.0 → 6.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +132 -27
- package/dist/index.js +229 -69
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -296,21 +296,12 @@ function stripReasoningFromMessages(messages) {
|
|
|
296
296
|
return rest;
|
|
297
297
|
});
|
|
298
298
|
}
|
|
299
|
-
function normalizeToolCalls(toolCalls,
|
|
299
|
+
function normalizeToolCalls(toolCalls, _bodyText, _status) {
|
|
300
300
|
if (!toolCalls || toolCalls.length === 0) return void 0;
|
|
301
301
|
const result = [];
|
|
302
302
|
for (let i = 0; i < toolCalls.length; i++) {
|
|
303
303
|
const tc = toolCalls[i];
|
|
304
304
|
const argsStr = tc?.function?.arguments ?? "{}";
|
|
305
|
-
try {
|
|
306
|
-
JSON.parse(argsStr);
|
|
307
|
-
} catch {
|
|
308
|
-
const excerpt = argsStr.slice(0, 500);
|
|
309
|
-
throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {
|
|
310
|
-
status,
|
|
311
|
-
body: bodyText.slice(0, 500)
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
305
|
result.push({
|
|
315
306
|
id: tc?.id ?? `call_${i}`,
|
|
316
307
|
type: "function",
|
|
@@ -394,14 +385,6 @@ function finalizeStreamChunk(accumulators, finishReason, usage) {
|
|
|
394
385
|
const acc = accumulators.get(idx);
|
|
395
386
|
if (!acc.id || !acc.name) continue;
|
|
396
387
|
const argsStr = acc.argsString || "{}";
|
|
397
|
-
try {
|
|
398
|
-
JSON.parse(argsStr);
|
|
399
|
-
} catch {
|
|
400
|
-
const excerpt = argsStr.slice(0, 500);
|
|
401
|
-
throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {
|
|
402
|
-
body: excerpt
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
388
|
toolCalls.push({
|
|
406
389
|
id: acc.id,
|
|
407
390
|
type: "function",
|
|
@@ -441,6 +424,9 @@ function isTracingToolResult(value) {
|
|
|
441
424
|
}
|
|
442
425
|
|
|
443
426
|
// src/reactLoop.ts
|
|
427
|
+
function isSubAgentToolResult(value) {
|
|
428
|
+
return typeof value === "object" && value !== null && value.__subAgent === true;
|
|
429
|
+
}
|
|
444
430
|
var ReactLoopError = class extends Error {
|
|
445
431
|
/** 配置的 maxTurns 值 */
|
|
446
432
|
maxTurns;
|
|
@@ -565,14 +551,15 @@ async function reactLoop(input, config) {
|
|
|
565
551
|
const extras = buildRequestExtras(config);
|
|
566
552
|
let totalUsage;
|
|
567
553
|
let finalReasoning;
|
|
568
|
-
let
|
|
554
|
+
let loopTurns = 0;
|
|
555
|
+
let subTurns = 0;
|
|
569
556
|
const traceStartedAt = enableTracing ? nowMs() : 0;
|
|
570
557
|
const traceEvents = enableTracing ? [] : void 0;
|
|
571
|
-
while (
|
|
558
|
+
while (loopTurns < maxTurns) {
|
|
572
559
|
if (config.signal?.aborted) {
|
|
573
560
|
throw new AgentAbortError(void 0, messages);
|
|
574
561
|
}
|
|
575
|
-
|
|
562
|
+
loopTurns++;
|
|
576
563
|
const llmStartedAt = enableTracing ? nowMs() : 0;
|
|
577
564
|
const outgoing = config.maxHistoryTokens ? trimHistory(messages, config.maxHistoryTokens) : messages;
|
|
578
565
|
const inputSnapshot = enableTracing ? [...outgoing] : void 0;
|
|
@@ -596,7 +583,7 @@ async function reactLoop(input, config) {
|
|
|
596
583
|
const llmEndedAt = nowMs();
|
|
597
584
|
traceEvents.push({
|
|
598
585
|
type: "llm_call",
|
|
599
|
-
turn:
|
|
586
|
+
turn: loopTurns,
|
|
600
587
|
startedAt: llmStartedAt,
|
|
601
588
|
durationMs: llmEndedAt - llmStartedAt,
|
|
602
589
|
model: config.model ?? "",
|
|
@@ -612,6 +599,7 @@ async function reactLoop(input, config) {
|
|
|
612
599
|
messages.push(stripReasoning(response.message));
|
|
613
600
|
if (response.stopReason !== "tool_calls" || !response.message.tool_calls) {
|
|
614
601
|
const traceEndedAt = enableTracing ? nowMs() : 0;
|
|
602
|
+
const turns = loopTurns + subTurns;
|
|
615
603
|
const result = {
|
|
616
604
|
content: response.message.content,
|
|
617
605
|
messages,
|
|
@@ -646,7 +634,9 @@ async function reactLoop(input, config) {
|
|
|
646
634
|
try {
|
|
647
635
|
args = parseToolCallArguments(toolCall);
|
|
648
636
|
rawResult = await config.executeTool(toolName, args);
|
|
649
|
-
if (
|
|
637
|
+
if (isSubAgentToolResult(rawResult)) {
|
|
638
|
+
resultStr = stringifyResult(rawResult.result);
|
|
639
|
+
} else if (isTracingToolResult(rawResult)) {
|
|
650
640
|
resultStr = stringifyResult(rawResult.result);
|
|
651
641
|
} else {
|
|
652
642
|
resultStr = stringifyResult(rawResult);
|
|
@@ -682,11 +672,31 @@ async function reactLoop(input, config) {
|
|
|
682
672
|
toolErr,
|
|
683
673
|
hasError
|
|
684
674
|
} of settled) {
|
|
675
|
+
if (isSubAgentToolResult(rawResult)) {
|
|
676
|
+
if (rawResult.usage) {
|
|
677
|
+
totalUsage = accumulateUsage(totalUsage, rawResult.usage);
|
|
678
|
+
}
|
|
679
|
+
if (typeof rawResult.turns === "number") {
|
|
680
|
+
subTurns += rawResult.turns;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
685
683
|
if (enableTracing) {
|
|
686
|
-
if (
|
|
684
|
+
if (isSubAgentToolResult(rawResult) && rawResult.trace) {
|
|
685
|
+
traceEvents.push({
|
|
686
|
+
type: "subagent_call",
|
|
687
|
+
turn: loopTurns,
|
|
688
|
+
startedAt: toolStartedAt,
|
|
689
|
+
durationMs: toolEndedAt - toolStartedAt,
|
|
690
|
+
toolCallId: toolCall.id,
|
|
691
|
+
agentName: extractSubAgentName(toolName),
|
|
692
|
+
input: JSON.stringify(args),
|
|
693
|
+
trace: rawResult.trace,
|
|
694
|
+
result: resultStr
|
|
695
|
+
});
|
|
696
|
+
} else if (isTracingToolResult(rawResult)) {
|
|
687
697
|
traceEvents.push({
|
|
688
698
|
type: "subagent_call",
|
|
689
|
-
turn:
|
|
699
|
+
turn: loopTurns,
|
|
690
700
|
startedAt: toolStartedAt,
|
|
691
701
|
durationMs: toolEndedAt - toolStartedAt,
|
|
692
702
|
toolCallId: toolCall.id,
|
|
@@ -698,7 +708,7 @@ async function reactLoop(input, config) {
|
|
|
698
708
|
} else {
|
|
699
709
|
traceEvents.push({
|
|
700
710
|
type: "tool_call",
|
|
701
|
-
turn:
|
|
711
|
+
turn: loopTurns,
|
|
702
712
|
startedAt: toolStartedAt,
|
|
703
713
|
durationMs: toolEndedAt - toolStartedAt,
|
|
704
714
|
toolCallId: toolCall.id,
|
|
@@ -728,12 +738,13 @@ async function* reactLoopStream(input, config) {
|
|
|
728
738
|
const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
729
739
|
const extras = buildRequestExtras(config);
|
|
730
740
|
let totalUsage;
|
|
731
|
-
let
|
|
732
|
-
|
|
741
|
+
let loopTurns = 0;
|
|
742
|
+
let subTurns = 0;
|
|
743
|
+
while (loopTurns < maxTurns) {
|
|
733
744
|
if (config.signal?.aborted) {
|
|
734
745
|
throw new AgentAbortError(void 0, messages);
|
|
735
746
|
}
|
|
736
|
-
|
|
747
|
+
loopTurns++;
|
|
737
748
|
const llmStartedAt = enableTracing ? nowMs() : 0;
|
|
738
749
|
const outgoing = config.maxHistoryTokens ? trimHistory(messages, config.maxHistoryTokens) : messages;
|
|
739
750
|
const inputSnapshot = enableTracing ? [...outgoing] : void 0;
|
|
@@ -763,7 +774,6 @@ async function* reactLoopStream(input, config) {
|
|
|
763
774
|
finishReason = chunk.finishReason;
|
|
764
775
|
}
|
|
765
776
|
if (chunk.usage) {
|
|
766
|
-
totalUsage = accumulateUsage(totalUsage, chunk.usage);
|
|
767
777
|
turnUsage = chunk.usage;
|
|
768
778
|
}
|
|
769
779
|
}
|
|
@@ -773,6 +783,9 @@ async function* reactLoopStream(input, config) {
|
|
|
773
783
|
}
|
|
774
784
|
throw err;
|
|
775
785
|
}
|
|
786
|
+
if (turnUsage) {
|
|
787
|
+
totalUsage = accumulateUsage(totalUsage, turnUsage);
|
|
788
|
+
}
|
|
776
789
|
const assistantMessage = {
|
|
777
790
|
role: "assistant",
|
|
778
791
|
content: turnContent
|
|
@@ -787,7 +800,7 @@ async function* reactLoopStream(input, config) {
|
|
|
787
800
|
yield {
|
|
788
801
|
traceEvent: {
|
|
789
802
|
type: "llm_call",
|
|
790
|
-
turn:
|
|
803
|
+
turn: loopTurns,
|
|
791
804
|
startedAt: llmStartedAt,
|
|
792
805
|
durationMs: llmEndedAt - llmStartedAt,
|
|
793
806
|
model: config.model ?? "",
|
|
@@ -801,7 +814,7 @@ async function* reactLoopStream(input, config) {
|
|
|
801
814
|
if (finishReason !== "tool_calls" || !toolCalls) {
|
|
802
815
|
const donePayload = {
|
|
803
816
|
content: turnContent,
|
|
804
|
-
turns,
|
|
817
|
+
turns: loopTurns + subTurns,
|
|
805
818
|
stopReason: finishReason ?? "other",
|
|
806
819
|
usage: totalUsage
|
|
807
820
|
};
|
|
@@ -819,11 +832,37 @@ async function* reactLoopStream(input, config) {
|
|
|
819
832
|
let rawResult;
|
|
820
833
|
let toolErr;
|
|
821
834
|
let hasError = false;
|
|
835
|
+
const pendingDeltas = [];
|
|
836
|
+
let wakeDrain = null;
|
|
837
|
+
const deltaEmitter = {
|
|
838
|
+
onSubAgentDelta: (delta) => {
|
|
839
|
+
pendingDeltas.push(delta);
|
|
840
|
+
const wake = wakeDrain;
|
|
841
|
+
wakeDrain = null;
|
|
842
|
+
wake?.();
|
|
843
|
+
}
|
|
844
|
+
};
|
|
822
845
|
try {
|
|
823
846
|
args = parseToolCallArguments(toolCall);
|
|
824
847
|
yield { toolCall: { name: toolName, arguments: args } };
|
|
825
|
-
|
|
826
|
-
|
|
848
|
+
const toolPromise = config.executeTool(toolName, args, deltaEmitter);
|
|
849
|
+
const settled = toolPromise.then(
|
|
850
|
+
() => true,
|
|
851
|
+
() => true
|
|
852
|
+
// 错误延后重抛,此处只关心执行结束以收尾 drain
|
|
853
|
+
);
|
|
854
|
+
drain: for (; ; ) {
|
|
855
|
+
while (pendingDeltas.length > 0) {
|
|
856
|
+
yield { subagentDelta: pendingDeltas.shift() };
|
|
857
|
+
}
|
|
858
|
+
if (await Promise.race([settled, new Promise((r) => wakeDrain = r)])) {
|
|
859
|
+
break drain;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
rawResult = await toolPromise;
|
|
863
|
+
if (isSubAgentToolResult(rawResult)) {
|
|
864
|
+
resultStr = stringifyResult(rawResult.result);
|
|
865
|
+
} else if (isTracingToolResult(rawResult)) {
|
|
827
866
|
resultStr = stringifyResult(rawResult.result);
|
|
828
867
|
} else {
|
|
829
868
|
resultStr = stringifyResult(rawResult);
|
|
@@ -834,14 +873,36 @@ async function* reactLoopStream(input, config) {
|
|
|
834
873
|
resultStr = stringifyError(err);
|
|
835
874
|
rawResult = void 0;
|
|
836
875
|
}
|
|
876
|
+
if (isSubAgentToolResult(rawResult)) {
|
|
877
|
+
if (rawResult.usage) {
|
|
878
|
+
totalUsage = accumulateUsage(totalUsage, rawResult.usage);
|
|
879
|
+
}
|
|
880
|
+
if (typeof rawResult.turns === "number") {
|
|
881
|
+
subTurns += rawResult.turns;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
837
884
|
yield { toolResult: { name: toolName, result: resultStr } };
|
|
838
885
|
if (enableTracing) {
|
|
839
886
|
const toolEndedAt = nowMs();
|
|
840
|
-
if (
|
|
887
|
+
if (isSubAgentToolResult(rawResult) && rawResult.trace) {
|
|
888
|
+
yield {
|
|
889
|
+
traceEvent: {
|
|
890
|
+
type: "subagent_call",
|
|
891
|
+
turn: loopTurns,
|
|
892
|
+
startedAt: toolStartedAt,
|
|
893
|
+
durationMs: toolEndedAt - toolStartedAt,
|
|
894
|
+
toolCallId: toolCall.id,
|
|
895
|
+
agentName: extractSubAgentName(toolName),
|
|
896
|
+
input: JSON.stringify(args),
|
|
897
|
+
trace: rawResult.trace,
|
|
898
|
+
result: resultStr
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
} else if (isTracingToolResult(rawResult)) {
|
|
841
902
|
yield {
|
|
842
903
|
traceEvent: {
|
|
843
904
|
type: "subagent_call",
|
|
844
|
-
turn:
|
|
905
|
+
turn: loopTurns,
|
|
845
906
|
startedAt: toolStartedAt,
|
|
846
907
|
durationMs: toolEndedAt - toolStartedAt,
|
|
847
908
|
toolCallId: toolCall.id,
|
|
@@ -855,7 +916,7 @@ async function* reactLoopStream(input, config) {
|
|
|
855
916
|
yield {
|
|
856
917
|
traceEvent: {
|
|
857
918
|
type: "tool_call",
|
|
858
|
-
turn:
|
|
919
|
+
turn: loopTurns,
|
|
859
920
|
startedAt: toolStartedAt,
|
|
860
921
|
durationMs: toolEndedAt - toolStartedAt,
|
|
861
922
|
toolCallId: toolCall.id,
|
|
@@ -933,6 +994,18 @@ var AgentError = class extends Error {
|
|
|
933
994
|
this.name = "AgentError";
|
|
934
995
|
}
|
|
935
996
|
};
|
|
997
|
+
var AgentToolTimeoutError = class extends AgentError {
|
|
998
|
+
/** 超时的 tool 名 */
|
|
999
|
+
toolName;
|
|
1000
|
+
/** 配置的超时毫秒数 */
|
|
1001
|
+
timeoutMs;
|
|
1002
|
+
constructor(toolName, timeoutMs) {
|
|
1003
|
+
super(`Tool "${toolName}" timed out after ${timeoutMs}ms`);
|
|
1004
|
+
this.name = "AgentToolTimeoutError";
|
|
1005
|
+
this.toolName = toolName;
|
|
1006
|
+
this.timeoutMs = timeoutMs;
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
936
1009
|
var AgentRecursionError = class extends AgentError {
|
|
937
1010
|
/** 配置的 maxAgentDepth 值 */
|
|
938
1011
|
maxDepth;
|
|
@@ -1075,6 +1148,22 @@ var Agent = class _Agent {
|
|
|
1075
1148
|
* `options.messages` 提供时先经 `validateResumeHistory` 结构校验,非法抛
|
|
1076
1149
|
* `AgentError`,不发起 LLM 请求。
|
|
1077
1150
|
*/
|
|
1151
|
+
/**
|
|
1152
|
+
* 构建执行白名单:agent 声明的 tools + sub-agents(`agent.` 前缀)
|
|
1153
|
+
*
|
|
1154
|
+
* 每次 run 构建一次存入 callCtx(executeTool 复用)——reload 场景注册表换代后
|
|
1155
|
+
* 新 run 重新构建,声明变化自然生效
|
|
1156
|
+
*/
|
|
1157
|
+
buildDeclaredTools(agentName) {
|
|
1158
|
+
const declared = /* @__PURE__ */ new Set();
|
|
1159
|
+
for (const tool of this.deps.resolveAgentTools(agentName)) {
|
|
1160
|
+
declared.add(tool.name);
|
|
1161
|
+
}
|
|
1162
|
+
for (const sub of this.deps.resolveSubAgents(agentName)) {
|
|
1163
|
+
declared.add(`agent.${sub.name}`);
|
|
1164
|
+
}
|
|
1165
|
+
return declared;
|
|
1166
|
+
}
|
|
1078
1167
|
async buildLoopConfig(input, options) {
|
|
1079
1168
|
if (!input && !options?.messages?.length) {
|
|
1080
1169
|
throw new AgentError(
|
|
@@ -1097,7 +1186,13 @@ var Agent = class _Agent {
|
|
|
1097
1186
|
const tools = await this.buildToolDefinitions(agentName);
|
|
1098
1187
|
const { provider, model } = options?.provider !== void 0 ? this.resolveExternalProvider(options.provider, options?.model) : this.resolveModelKey(options?.model, meta);
|
|
1099
1188
|
const enableTracing = options?.enableTracing ?? this.deps.config?.enableTracing ?? false;
|
|
1100
|
-
const callCtx = {
|
|
1189
|
+
const callCtx = {
|
|
1190
|
+
agentName,
|
|
1191
|
+
enableTracing,
|
|
1192
|
+
provider,
|
|
1193
|
+
model,
|
|
1194
|
+
declaredTools: this.buildDeclaredTools(agentName)
|
|
1195
|
+
};
|
|
1101
1196
|
return {
|
|
1102
1197
|
provider,
|
|
1103
1198
|
systemPrompt: meta.systemPrompt,
|
|
@@ -1105,11 +1200,12 @@ var Agent = class _Agent {
|
|
|
1105
1200
|
temperature: options?.temperature,
|
|
1106
1201
|
maxTokens: options?.maxTokens,
|
|
1107
1202
|
maxTurns: meta.maxTurns ?? this.deps.config?.maxTurns,
|
|
1203
|
+
maxHistoryTokens: this.deps.config?.maxHistoryTokens,
|
|
1108
1204
|
tools,
|
|
1109
1205
|
signal: options?.signal,
|
|
1110
1206
|
messages: options?.messages,
|
|
1111
1207
|
enableTracing,
|
|
1112
|
-
executeTool: async (name, args) => this.executeTool(name, args, callCtx)
|
|
1208
|
+
executeTool: async (name, args, deltaEmitter) => this.executeTool(name, args, callCtx, deltaEmitter)
|
|
1113
1209
|
};
|
|
1114
1210
|
}
|
|
1115
1211
|
/**
|
|
@@ -1265,7 +1361,7 @@ var Agent = class _Agent {
|
|
|
1265
1361
|
/**
|
|
1266
1362
|
* tool 执行路由(由 reactLoop 调用)
|
|
1267
1363
|
*
|
|
1268
|
-
* - `agent.` 前缀 → {@link executeSubAgent} 递归(含
|
|
1364
|
+
* - `agent.` 前缀 → {@link executeSubAgent} 递归(含 usage/turns 上卷 + tracing 包装)
|
|
1269
1365
|
* - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用
|
|
1270
1366
|
*
|
|
1271
1367
|
* `callCtx` 由 [buildLoopConfig](#buildLoopConfig) 闭包捕获传入——本次调用的有效
|
|
@@ -1277,7 +1373,7 @@ var Agent = class _Agent {
|
|
|
1277
1373
|
*
|
|
1278
1374
|
* **tool 未找到 / 加载失败**:抛错,被 reactLoop catch 后同样回传 LLM。
|
|
1279
1375
|
*/
|
|
1280
|
-
async executeTool(rawName, rawArgs, callCtx) {
|
|
1376
|
+
async executeTool(rawName, rawArgs, callCtx, deltaEmitter) {
|
|
1281
1377
|
const name = rawName;
|
|
1282
1378
|
let args = rawArgs;
|
|
1283
1379
|
const guard = this.deps.config?.beforeToolCall?.(name, args, this.deps.ctx);
|
|
@@ -1285,20 +1381,13 @@ var Agent = class _Agent {
|
|
|
1285
1381
|
if ("error" in guard) return { error: guard.error };
|
|
1286
1382
|
if ("args" in guard) args = guard.args;
|
|
1287
1383
|
}
|
|
1288
|
-
|
|
1289
|
-
for (const tool2 of this.deps.resolveAgentTools(callCtx.agentName)) {
|
|
1290
|
-
declared.add(tool2.name);
|
|
1291
|
-
}
|
|
1292
|
-
for (const sub of this.deps.resolveSubAgents(callCtx.agentName)) {
|
|
1293
|
-
declared.add(`agent.${sub.name}`);
|
|
1294
|
-
}
|
|
1295
|
-
if (!declared.has(name)) {
|
|
1384
|
+
if (!callCtx.declaredTools.has(name)) {
|
|
1296
1385
|
return {
|
|
1297
1386
|
error: `Tool "${name}" is not declared by agent "${callCtx.agentName}" (add it to the agent's tools/agents declaration)`
|
|
1298
1387
|
};
|
|
1299
1388
|
}
|
|
1300
1389
|
if (name.startsWith("agent.")) {
|
|
1301
|
-
return await this.executeSubAgent(name.slice(6), args, callCtx);
|
|
1390
|
+
return await this.executeSubAgent(name.slice(6), args, callCtx, deltaEmitter);
|
|
1302
1391
|
}
|
|
1303
1392
|
const tool = this.deps.getTool(name);
|
|
1304
1393
|
if (!tool) {
|
|
@@ -1314,27 +1403,49 @@ var Agent = class _Agent {
|
|
|
1314
1403
|
callArgs = result2.value ?? args;
|
|
1315
1404
|
}
|
|
1316
1405
|
const mod = await this.deps.loadToolModule(tool.filePath, tool.functionName);
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1406
|
+
const toolTimeoutMs = this.deps.config?.toolTimeoutMs;
|
|
1407
|
+
let result;
|
|
1408
|
+
if (toolTimeoutMs && toolTimeoutMs > 0) {
|
|
1409
|
+
result = await Promise.race([
|
|
1410
|
+
mod.handler(callArgs, this.deps.ctx),
|
|
1411
|
+
new Promise((_, reject) => {
|
|
1412
|
+
const timer = setTimeout(
|
|
1413
|
+
() => reject(new AgentToolTimeoutError(name, toolTimeoutMs)),
|
|
1414
|
+
toolTimeoutMs
|
|
1415
|
+
);
|
|
1416
|
+
timer.unref?.();
|
|
1417
|
+
})
|
|
1418
|
+
]);
|
|
1419
|
+
} else {
|
|
1420
|
+
result = await mod.handler(callArgs, this.deps.ctx);
|
|
1421
|
+
}
|
|
1422
|
+
try {
|
|
1423
|
+
this.deps.config?.afterToolCall?.(name, args, result, this.deps.ctx);
|
|
1424
|
+
} catch (hookErr) {
|
|
1425
|
+
console.error(`[faapi] afterToolCall hook error for tool "${name}":`, hookErr);
|
|
1426
|
+
}
|
|
1319
1427
|
return result;
|
|
1320
1428
|
}
|
|
1321
1429
|
/**
|
|
1322
1430
|
* sub-agent 递归执行
|
|
1323
1431
|
*
|
|
1324
1432
|
* 1. `maxAgentDepth` 防护——超限抛 {@link AgentRecursionError}
|
|
1325
|
-
* 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`(无 trace
|
|
1433
|
+
* 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`(无 trace、无结构化
|
|
1434
|
+
* usage 可卷——直接返回业务结果,其 token 不进入父 run 台账)
|
|
1326
1435
|
* 3. 无 `run` 时调 `subAgent.run(stringify(args), { agent, provider, model, enableTracing })`
|
|
1327
1436
|
* 走默认 reactLoop——继承父调用的 provider,sub 元数据声明 `model` 时优先用自身的,
|
|
1328
1437
|
* 未声明时沿用父 model
|
|
1329
1438
|
*
|
|
1330
|
-
*
|
|
1331
|
-
*
|
|
1332
|
-
* reactLoop
|
|
1333
|
-
*
|
|
1334
|
-
*
|
|
1439
|
+
* **返回值统一包装为 [SubAgentToolResult](./reactLoop.md)**(无论 tracing 开关——
|
|
1440
|
+
* 用量上卷不依赖 tracing):`usage` / `turns` 是子循环整树口径(sub-sub 已在子循环
|
|
1441
|
+
* 上卷),reactLoop 识别后累加进父循环,父 run 的 usage 台账 = 全部 `llm_call` 之和。
|
|
1442
|
+
* `enableTracing=true` 时再附 `trace`(agentName 已被 `Agent.run` 填为 subName),
|
|
1443
|
+
* reactLoop 据此发出 `subagent_call` 事件,嵌入 sub-trace(递归结构,业务方可还原
|
|
1444
|
+
* 完整调用树)。详见 reactLoop.md「usage 与 turns 的整树口径」。
|
|
1335
1445
|
*
|
|
1336
|
-
* **自定义 run 无 trace
|
|
1337
|
-
* 内部明细——需 trace
|
|
1446
|
+
* **自定义 run 无 trace、无用量上卷**:业务方导出 `run` 函数时直接返回业务结果,
|
|
1447
|
+
* 无法采集 sub-agent 内部明细——需 trace / 用量统计时让 sub-agent 走默认 reactLoop
|
|
1448
|
+
* (不导出 `run`)。
|
|
1338
1449
|
*
|
|
1339
1450
|
* 自定义 run 接收原始 args 对象;默认 reactLoop 的 user 消息:args 恰为单字段
|
|
1340
1451
|
* `{ input: <string> }`(与显式入参 schema 形状一致)时直传字符串,其余形状
|
|
@@ -1344,7 +1455,7 @@ var Agent = class _Agent {
|
|
|
1344
1455
|
* 而非 `getAgent`(返回 AgentCore,无代码加载细节)。DB skill 无文件,
|
|
1345
1456
|
* `getAgentEntry` 返回 `undefined`,走默认 reactLoop。
|
|
1346
1457
|
*/
|
|
1347
|
-
async executeSubAgent(subName, args, callCtx) {
|
|
1458
|
+
async executeSubAgent(subName, args, callCtx, deltaEmitter) {
|
|
1348
1459
|
const newDepth = this.depth + 1;
|
|
1349
1460
|
const maxDepth = this.deps.config?.maxAgentDepth ?? DEFAULT_MAX_AGENT_DEPTH;
|
|
1350
1461
|
if (newDepth > maxDepth) {
|
|
@@ -1361,21 +1472,68 @@ var Agent = class _Agent {
|
|
|
1361
1472
|
}
|
|
1362
1473
|
}
|
|
1363
1474
|
const subMeta = this.deps.getAgent(subName);
|
|
1364
|
-
const
|
|
1475
|
+
const subOptions = {
|
|
1365
1476
|
agent: subName,
|
|
1366
1477
|
provider: callCtx.provider,
|
|
1367
1478
|
model: subMeta?.model ?? callCtx.model,
|
|
1368
1479
|
enableTracing: callCtx.enableTracing
|
|
1369
|
-
}
|
|
1480
|
+
};
|
|
1481
|
+
if (deltaEmitter) {
|
|
1482
|
+
const toolName = `agent.${subName}`;
|
|
1483
|
+
const startedAt = performance.now();
|
|
1484
|
+
const traceEvents = callCtx.enableTracing ? [] : void 0;
|
|
1485
|
+
let done;
|
|
1486
|
+
for await (const chunk of subAgent.stream(extractSubAgentUserInput(args), subOptions)) {
|
|
1487
|
+
if (chunk.subagentDelta) {
|
|
1488
|
+
deltaEmitter.onSubAgentDelta(chunk.subagentDelta);
|
|
1489
|
+
} else if (typeof chunk.deltaContent === "string" && chunk.deltaContent.length > 0 || typeof chunk.deltaReasoning === "string" && chunk.deltaReasoning.length > 0) {
|
|
1490
|
+
deltaEmitter.onSubAgentDelta({
|
|
1491
|
+
name: toolName,
|
|
1492
|
+
depth: newDepth,
|
|
1493
|
+
deltaContent: chunk.deltaContent,
|
|
1494
|
+
deltaReasoning: chunk.deltaReasoning
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
if (chunk.traceEvent) {
|
|
1498
|
+
traceEvents.push(chunk.traceEvent);
|
|
1499
|
+
}
|
|
1500
|
+
if (chunk.done) {
|
|
1501
|
+
done = chunk.done;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
this.deps.config?.afterToolCall?.(toolName, args, done?.content, this.deps.ctx);
|
|
1505
|
+
const wrapped2 = {
|
|
1506
|
+
__subAgent: true,
|
|
1507
|
+
result: done?.content ?? "",
|
|
1508
|
+
usage: done?.usage,
|
|
1509
|
+
turns: done?.turns ?? 0
|
|
1510
|
+
};
|
|
1511
|
+
if (traceEvents) {
|
|
1512
|
+
wrapped2.trace = {
|
|
1513
|
+
agentName: "",
|
|
1514
|
+
startedAt,
|
|
1515
|
+
durationMs: performance.now() - startedAt,
|
|
1516
|
+
turns: done?.turns ?? 0,
|
|
1517
|
+
usage: done?.usage,
|
|
1518
|
+
stopReason: done?.stopReason,
|
|
1519
|
+
content: done?.content,
|
|
1520
|
+
events: traceEvents
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
return wrapped2;
|
|
1524
|
+
}
|
|
1525
|
+
const result = await subAgent.run(extractSubAgentUserInput(args), subOptions);
|
|
1370
1526
|
this.deps.config?.afterToolCall?.(`agent.${subName}`, args, result.content, this.deps.ctx);
|
|
1527
|
+
const wrapped = {
|
|
1528
|
+
__subAgent: true,
|
|
1529
|
+
result: result.content,
|
|
1530
|
+
usage: result.usage,
|
|
1531
|
+
turns: result.turns
|
|
1532
|
+
};
|
|
1371
1533
|
if (callCtx.enableTracing && result.trace) {
|
|
1372
|
-
|
|
1373
|
-
__trace: true,
|
|
1374
|
-
result: result.content,
|
|
1375
|
-
trace: result.trace
|
|
1376
|
-
};
|
|
1534
|
+
wrapped.trace = result.trace;
|
|
1377
1535
|
}
|
|
1378
|
-
return
|
|
1536
|
+
return wrapped;
|
|
1379
1537
|
}
|
|
1380
1538
|
};
|
|
1381
1539
|
|
|
@@ -1494,12 +1652,14 @@ export {
|
|
|
1494
1652
|
AgentAbortError,
|
|
1495
1653
|
AgentError,
|
|
1496
1654
|
AgentRecursionError,
|
|
1655
|
+
AgentToolTimeoutError,
|
|
1497
1656
|
LLMProviderError,
|
|
1498
1657
|
ReactLoopError,
|
|
1499
1658
|
createOpenAIProvider,
|
|
1500
1659
|
createProvider,
|
|
1501
1660
|
createToolSchemaResolver,
|
|
1502
1661
|
plugin_default as default,
|
|
1662
|
+
isSubAgentToolResult,
|
|
1503
1663
|
isTracingToolResult,
|
|
1504
1664
|
reactLoop,
|
|
1505
1665
|
reactLoopStream
|