@faapi/agent 5.4.0 → 6.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/dist/index.js CHANGED
@@ -30,10 +30,12 @@ function createOpenAIProvider(config) {
30
30
  const modelConfig = modelName ? config.models[modelName] : void 0;
31
31
  const body = {
32
32
  model: modelName,
33
- messages: request.messages.map(toOpenAIMessage)
33
+ // 规范形即 OpenAI 形状:messages 恒等透传(剥离框架扩展字段 reasoning_content,
34
+ // 推理内容是解析产物不回传——DeepSeek 多轮回传直接 400,OpenAI 等拒绝未知字段)
35
+ messages: stripReasoningFromMessages(request.messages)
34
36
  };
35
37
  if (request.tools && request.tools.length > 0) {
36
- body.tools = request.tools.map(toOpenAITool);
38
+ body.tools = request.tools;
37
39
  }
38
40
  const mergedConfig = {};
39
41
  for (const key of Object.keys(config)) {
@@ -182,13 +184,13 @@ function createOpenAIProvider(config) {
182
184
  });
183
185
  }
184
186
  const content = msg.content ?? "";
185
- const toolCalls = parseToolCalls(msg.tool_calls, bodyText, response.status);
187
+ const toolCalls = normalizeToolCalls(msg.tool_calls, bodyText, response.status);
188
+ const message = { role: "assistant", content };
189
+ if (toolCalls) message.tool_calls = toolCalls;
190
+ const reasoning = extractReasoning(msg);
191
+ if (reasoning) message.reasoning_content = reasoning;
186
192
  return {
187
- message: {
188
- role: "assistant",
189
- content,
190
- toolCalls
191
- },
193
+ message,
192
194
  stopReason: mapStopReason(choice.finish_reason ?? void 0),
193
195
  usage: mapUsage(json.usage)
194
196
  };
@@ -243,6 +245,10 @@ function createOpenAIProvider(config) {
243
245
  }
244
246
  const delta = chunk.choices?.[0]?.delta;
245
247
  if (delta) {
248
+ const reasoning = extractReasoning(delta);
249
+ if (reasoning) {
250
+ yield { deltaReasoning: reasoning };
251
+ }
246
252
  if (typeof delta.content === "string" && delta.content.length > 0) {
247
253
  yield { deltaContent: delta.content };
248
254
  }
@@ -271,40 +277,33 @@ function createOpenAIProvider(config) {
271
277
  }
272
278
  return { complete, stream };
273
279
  }
274
- function toOpenAIMessage(msg) {
275
- const out = {
276
- role: msg.role,
277
- content: msg.content
278
- };
279
- if (msg.toolCallId !== void 0) out.tool_call_id = msg.toolCallId;
280
- if (msg.toolCalls !== void 0 && msg.toolCalls.length > 0) {
281
- out.tool_calls = msg.toolCalls.map((tc) => ({
282
- id: tc.id,
283
- type: "function",
284
- function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
285
- }));
280
+ function extractReasoning(source) {
281
+ if (typeof source.reasoning_content === "string" && source.reasoning_content.length > 0) {
282
+ return source.reasoning_content;
286
283
  }
287
- return out;
284
+ if (typeof source.reasoning === "string" && source.reasoning.length > 0) {
285
+ return source.reasoning;
286
+ }
287
+ return void 0;
288
288
  }
289
- function toOpenAITool(tool) {
290
- return {
291
- type: "function",
292
- function: {
293
- name: tool.name,
294
- description: tool.description,
295
- parameters: tool.input
296
- }
297
- };
289
+ function stripReasoningFromMessages(messages) {
290
+ if (!messages.some((m) => m.reasoning_content !== void 0)) {
291
+ return messages;
292
+ }
293
+ return messages.map((m) => {
294
+ if (m.reasoning_content === void 0) return m;
295
+ const { reasoning_content: _stripped, ...rest } = m;
296
+ return rest;
297
+ });
298
298
  }
299
- function parseToolCalls(toolCalls, bodyText, status) {
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
- let args;
306
305
  try {
307
- args = JSON.parse(argsStr);
306
+ JSON.parse(argsStr);
308
307
  } catch {
309
308
  const excerpt = argsStr.slice(0, 500);
310
309
  throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {
@@ -314,8 +313,8 @@ function parseToolCalls(toolCalls, bodyText, status) {
314
313
  }
315
314
  result.push({
316
315
  id: tc?.id ?? `call_${i}`,
317
- name: tc?.function?.name ?? "",
318
- arguments: args
316
+ type: "function",
317
+ function: { name: tc?.function?.name ?? "", arguments: argsStr }
319
318
  });
320
319
  }
321
320
  return result;
@@ -337,9 +336,9 @@ function mapStopReason(fr) {
337
336
  function mapUsage(u) {
338
337
  if (!u) return void 0;
339
338
  return {
340
- promptTokens: u.prompt_tokens ?? 0,
341
- completionTokens: u.completion_tokens ?? 0,
342
- totalTokens: u.total_tokens ?? 0
339
+ prompt_tokens: u.prompt_tokens ?? 0,
340
+ completion_tokens: u.completion_tokens ?? 0,
341
+ total_tokens: u.total_tokens ?? 0
343
342
  };
344
343
  }
345
344
  function extractSSEData(event) {
@@ -394,19 +393,19 @@ function finalizeStreamChunk(accumulators, finishReason, usage) {
394
393
  for (const idx of indices) {
395
394
  const acc = accumulators.get(idx);
396
395
  if (!acc.id || !acc.name) continue;
397
- let args;
396
+ const argsStr = acc.argsString || "{}";
398
397
  try {
399
- args = acc.argsString ? JSON.parse(acc.argsString) : {};
398
+ JSON.parse(argsStr);
400
399
  } catch {
401
- const excerpt = acc.argsString.slice(0, 500);
400
+ const excerpt = argsStr.slice(0, 500);
402
401
  throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {
403
402
  body: excerpt
404
403
  });
405
404
  }
406
405
  toolCalls.push({
407
406
  id: acc.id,
408
- name: acc.name,
409
- arguments: args
407
+ type: "function",
408
+ function: { name: acc.name, arguments: argsStr }
410
409
  });
411
410
  }
412
411
  }
@@ -464,12 +463,17 @@ function stringifyError(err) {
464
463
  if (err instanceof Error) return err.message;
465
464
  return String(err);
466
465
  }
466
+ function stripReasoning(message) {
467
+ if (message.reasoning_content === void 0) return message;
468
+ const { reasoning_content: _stripped, ...rest } = message;
469
+ return rest;
470
+ }
467
471
  function accumulateUsage(a, b) {
468
472
  if (!a) return { ...b };
469
473
  return {
470
- promptTokens: a.promptTokens + b.promptTokens,
471
- completionTokens: a.completionTokens + b.completionTokens,
472
- totalTokens: a.totalTokens + b.totalTokens
474
+ prompt_tokens: a.prompt_tokens + b.prompt_tokens,
475
+ completion_tokens: a.completion_tokens + b.completion_tokens,
476
+ total_tokens: a.total_tokens + b.total_tokens
473
477
  };
474
478
  }
475
479
  function buildInitialMessages(input, systemPrompt) {
@@ -480,9 +484,14 @@ function buildInitialMessages(input, systemPrompt) {
480
484
  messages.push({ role: "user", content: input });
481
485
  return messages;
482
486
  }
487
+ function parseToolCallArguments(toolCall) {
488
+ const raw = toolCall.function.arguments;
489
+ if (!raw) return {};
490
+ return JSON.parse(raw);
491
+ }
483
492
  function initLoopMessages(input, config) {
484
493
  if (config.messages?.length) {
485
- const messages = [...config.messages];
494
+ const messages = config.messages.map(stripReasoning);
486
495
  if (config.systemPrompt && !messages.some((m) => m.role === "system")) {
487
496
  messages.unshift({ role: "system", content: config.systemPrompt });
488
497
  }
@@ -498,11 +507,11 @@ function estimateTokens(chars) {
498
507
  }
499
508
  function estimateMessageTokens(message) {
500
509
  let chars = message.content.length;
501
- if (message.toolCalls) {
502
- chars += JSON.stringify(message.toolCalls).length;
510
+ if (message.tool_calls) {
511
+ chars += JSON.stringify(message.tool_calls).length;
503
512
  }
504
- if (message.toolCallId) {
505
- chars += message.toolCallId.length;
513
+ if (message.tool_call_id) {
514
+ chars += message.tool_call_id.length;
506
515
  }
507
516
  return estimateTokens(chars);
508
517
  }
@@ -555,6 +564,7 @@ async function reactLoop(input, config) {
555
564
  const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
556
565
  const extras = buildRequestExtras(config);
557
566
  let totalUsage;
567
+ let finalReasoning;
558
568
  let turns = 0;
559
569
  const traceStartedAt = enableTracing ? nowMs() : 0;
560
570
  const traceEvents = enableTracing ? [] : void 0;
@@ -591,15 +601,18 @@ async function reactLoop(input, config) {
591
601
  durationMs: llmEndedAt - llmStartedAt,
592
602
  model: config.model ?? "",
593
603
  inputMessages: inputSnapshot,
604
+ // 原始 assistant 消息(含 reasoning_content)——trace 保留完整 LLM 返回,
605
+ // 与历史剥离策略互补(见 trace.md「llm_call.response 与 thinking」)
594
606
  response: response.message,
595
607
  stopReason: response.stopReason,
596
608
  usage: response.usage
597
609
  });
598
610
  }
599
- messages.push(response.message);
600
- if (response.stopReason !== "tool_calls" || !response.message.toolCalls) {
611
+ finalReasoning = response.message.reasoning_content;
612
+ messages.push(stripReasoning(response.message));
613
+ if (response.stopReason !== "tool_calls" || !response.message.tool_calls) {
601
614
  const traceEndedAt = enableTracing ? nowMs() : 0;
602
- return {
615
+ const result = {
603
616
  content: response.message.content,
604
617
  messages,
605
618
  turns,
@@ -616,16 +629,23 @@ async function reactLoop(input, config) {
616
629
  events: traceEvents
617
630
  } : void 0
618
631
  };
632
+ if (finalReasoning !== void 0) {
633
+ result.reasoning = finalReasoning;
634
+ }
635
+ return result;
619
636
  }
620
637
  const settled = await Promise.all(
621
- response.message.toolCalls.map(async (toolCall) => {
638
+ response.message.tool_calls.map(async (toolCall) => {
622
639
  const toolStartedAt = enableTracing ? nowMs() : 0;
640
+ const toolName = toolCall.function.name;
641
+ let args = {};
623
642
  let resultStr;
624
643
  let rawResult;
625
644
  let toolErr;
626
645
  let hasError = false;
627
646
  try {
628
- rawResult = await config.executeTool(toolCall.name, toolCall.arguments);
647
+ args = parseToolCallArguments(toolCall);
648
+ rawResult = await config.executeTool(toolName, args);
629
649
  if (isTracingToolResult(rawResult)) {
630
650
  resultStr = stringifyResult(rawResult.result);
631
651
  } else {
@@ -640,6 +660,8 @@ async function reactLoop(input, config) {
640
660
  const toolEndedAt = enableTracing ? nowMs() : 0;
641
661
  return {
642
662
  toolCall,
663
+ toolName,
664
+ args,
643
665
  toolStartedAt,
644
666
  toolEndedAt,
645
667
  resultStr,
@@ -651,6 +673,8 @@ async function reactLoop(input, config) {
651
673
  );
652
674
  for (const {
653
675
  toolCall,
676
+ toolName,
677
+ args,
654
678
  toolStartedAt,
655
679
  toolEndedAt,
656
680
  resultStr,
@@ -666,8 +690,8 @@ async function reactLoop(input, config) {
666
690
  startedAt: toolStartedAt,
667
691
  durationMs: toolEndedAt - toolStartedAt,
668
692
  toolCallId: toolCall.id,
669
- agentName: extractSubAgentName(toolCall.name),
670
- input: JSON.stringify(toolCall.arguments),
693
+ agentName: extractSubAgentName(toolName),
694
+ input: JSON.stringify(args),
671
695
  trace: rawResult.trace,
672
696
  result: resultStr
673
697
  });
@@ -678,8 +702,8 @@ async function reactLoop(input, config) {
678
702
  startedAt: toolStartedAt,
679
703
  durationMs: toolEndedAt - toolStartedAt,
680
704
  toolCallId: toolCall.id,
681
- name: toolCall.name,
682
- arguments: toolCall.arguments,
705
+ name: toolName,
706
+ arguments: args,
683
707
  result: resultStr,
684
708
  error: hasError ? stringifyError(toolErr) : void 0
685
709
  });
@@ -688,7 +712,7 @@ async function reactLoop(input, config) {
688
712
  messages.push({
689
713
  role: "tool",
690
714
  content: resultStr,
691
- toolCallId: toolCall.id
715
+ tool_call_id: toolCall.id
692
716
  });
693
717
  }
694
718
  }
@@ -714,6 +738,7 @@ async function* reactLoopStream(input, config) {
714
738
  const outgoing = config.maxHistoryTokens ? trimHistory(messages, config.maxHistoryTokens) : messages;
715
739
  const inputSnapshot = enableTracing ? [...outgoing] : void 0;
716
740
  let turnContent = "";
741
+ let turnReasoning = "";
717
742
  let toolCalls;
718
743
  let finishReason;
719
744
  let turnUsage;
@@ -723,6 +748,10 @@ async function* reactLoopStream(input, config) {
723
748
  ...extras,
724
749
  signal: config.signal
725
750
  })) {
751
+ if (typeof chunk.deltaReasoning === "string" && chunk.deltaReasoning.length > 0) {
752
+ turnReasoning += chunk.deltaReasoning;
753
+ yield { deltaReasoning: chunk.deltaReasoning };
754
+ }
726
755
  if (typeof chunk.deltaContent === "string" && chunk.deltaContent.length > 0) {
727
756
  turnContent += chunk.deltaContent;
728
757
  yield { deltaContent: chunk.deltaContent };
@@ -749,11 +778,12 @@ async function* reactLoopStream(input, config) {
749
778
  content: turnContent
750
779
  };
751
780
  if (toolCalls) {
752
- assistantMessage.toolCalls = toolCalls;
781
+ assistantMessage.tool_calls = toolCalls;
753
782
  }
754
783
  messages.push(assistantMessage);
755
784
  if (enableTracing) {
756
785
  const llmEndedAt = nowMs();
786
+ const tracedResponse = turnReasoning ? { ...assistantMessage, reasoning_content: turnReasoning } : assistantMessage;
757
787
  yield {
758
788
  traceEvent: {
759
789
  type: "llm_call",
@@ -762,32 +792,37 @@ async function* reactLoopStream(input, config) {
762
792
  durationMs: llmEndedAt - llmStartedAt,
763
793
  model: config.model ?? "",
764
794
  inputMessages: inputSnapshot,
765
- response: assistantMessage,
795
+ response: tracedResponse,
766
796
  stopReason: finishReason ?? "other",
767
797
  usage: turnUsage
768
798
  }
769
799
  };
770
800
  }
771
801
  if (finishReason !== "tool_calls" || !toolCalls) {
772
- yield {
773
- done: {
774
- content: turnContent,
775
- turns,
776
- stopReason: finishReason ?? "other",
777
- usage: totalUsage
778
- }
802
+ const donePayload = {
803
+ content: turnContent,
804
+ turns,
805
+ stopReason: finishReason ?? "other",
806
+ usage: totalUsage
779
807
  };
808
+ if (turnReasoning) {
809
+ donePayload.reasoning = turnReasoning;
810
+ }
811
+ yield { done: donePayload };
780
812
  return;
781
813
  }
782
814
  for (const toolCall of toolCalls) {
783
- yield { toolCall: { name: toolCall.name, arguments: toolCall.arguments } };
815
+ const toolName = toolCall.function.name;
784
816
  const toolStartedAt = enableTracing ? nowMs() : 0;
817
+ let args = {};
785
818
  let resultStr;
786
819
  let rawResult;
787
820
  let toolErr;
788
821
  let hasError = false;
789
822
  try {
790
- rawResult = await config.executeTool(toolCall.name, toolCall.arguments);
823
+ args = parseToolCallArguments(toolCall);
824
+ yield { toolCall: { name: toolName, arguments: args } };
825
+ rawResult = await config.executeTool(toolName, args);
791
826
  if (isTracingToolResult(rawResult)) {
792
827
  resultStr = stringifyResult(rawResult.result);
793
828
  } else {
@@ -799,7 +834,7 @@ async function* reactLoopStream(input, config) {
799
834
  resultStr = stringifyError(err);
800
835
  rawResult = void 0;
801
836
  }
802
- yield { toolResult: { name: toolCall.name, result: resultStr } };
837
+ yield { toolResult: { name: toolName, result: resultStr } };
803
838
  if (enableTracing) {
804
839
  const toolEndedAt = nowMs();
805
840
  if (isTracingToolResult(rawResult)) {
@@ -810,8 +845,8 @@ async function* reactLoopStream(input, config) {
810
845
  startedAt: toolStartedAt,
811
846
  durationMs: toolEndedAt - toolStartedAt,
812
847
  toolCallId: toolCall.id,
813
- agentName: extractSubAgentName(toolCall.name),
814
- input: JSON.stringify(toolCall.arguments),
848
+ agentName: extractSubAgentName(toolName),
849
+ input: JSON.stringify(args),
815
850
  trace: rawResult.trace,
816
851
  result: resultStr
817
852
  }
@@ -824,8 +859,8 @@ async function* reactLoopStream(input, config) {
824
859
  startedAt: toolStartedAt,
825
860
  durationMs: toolEndedAt - toolStartedAt,
826
861
  toolCallId: toolCall.id,
827
- name: toolCall.name,
828
- arguments: toolCall.arguments,
862
+ name: toolName,
863
+ arguments: args,
829
864
  result: resultStr,
830
865
  error: hasError ? stringifyError(toolErr) : void 0
831
866
  }
@@ -835,7 +870,7 @@ async function* reactLoopStream(input, config) {
835
870
  messages.push({
836
871
  role: "tool",
837
872
  content: resultStr,
838
- toolCallId: toolCall.id
873
+ tool_call_id: toolCall.id
839
874
  });
840
875
  }
841
876
  }
@@ -871,10 +906,10 @@ function validateResumeHistory(messages) {
871
906
  `Invalid resume history at messages[${index}]: unknown role "${String(message.role)}"`
872
907
  );
873
908
  }
874
- if (message.role === "assistant" && message.toolCalls?.length) {
875
- const missing = new Set(message.toolCalls.map((call) => call.id));
909
+ if (message.role === "assistant" && message.tool_calls?.length) {
910
+ const missing = new Set(message.tool_calls.map((call) => call.id));
876
911
  for (let j = index + 1; j < messages.length && messages[j].role === "tool"; j++) {
877
- missing.delete(messages[j].toolCallId ?? "");
912
+ missing.delete(messages[j].tool_call_id ?? "");
878
913
  }
879
914
  if (missing.size > 0) {
880
915
  throw new AgentError(
@@ -1166,17 +1201,17 @@ var Agent = class _Agent {
1166
1201
  );
1167
1202
  }
1168
1203
  /**
1169
- * 组装 LLM 可见 tool 列表
1204
+ * 组装 LLM 可见 tool 列表(OpenAI chat completions 规范形)
1170
1205
  *
1171
- * 合并两个来源(按 `name` 去重,先入者保留):
1206
+ * 合并两个来源(按 `function.name` 去重,先入者保留):
1172
1207
  * 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用
1173
1208
  * 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`
1174
1209
  *
1175
- * 每个常规 tool 的 `input`:
1210
+ * 每个常规 tool 的 `function.parameters`:
1176
1211
  * - `resolveToolSchema` 提供 → 用其 `jsonSchema`
1177
1212
  * - 未提供 / tool 无 `inputTypeName` → 自由 schema `{ type: 'object' }`
1178
1213
  *
1179
- * sub-agent 的 `input` 始终为 `{ type: 'object' }`(agent 参数开放)。
1214
+ * sub-agent 的 `function.parameters` 始终为 `{ type: 'object' }`(agent 参数开放)。
1180
1215
  */
1181
1216
  async buildToolDefinitions(agentName) {
1182
1217
  const definitions = /* @__PURE__ */ new Map();
@@ -1184,18 +1219,24 @@ var Agent = class _Agent {
1184
1219
  if (definitions.has(tool.name)) continue;
1185
1220
  const schemaRes = await this.getToolSchema(tool);
1186
1221
  definitions.set(tool.name, {
1187
- name: tool.name,
1188
- description: tool.description,
1189
- input: schemaRes?.jsonSchema ?? { type: "object" }
1222
+ type: "function",
1223
+ function: {
1224
+ name: tool.name,
1225
+ description: tool.description,
1226
+ parameters: schemaRes?.jsonSchema ?? { type: "object" }
1227
+ }
1190
1228
  });
1191
1229
  }
1192
1230
  for (const subAgent of this.deps.resolveSubAgents(agentName)) {
1193
1231
  const name = `agent.${subAgent.name}`;
1194
1232
  if (definitions.has(name)) continue;
1195
1233
  definitions.set(name, {
1196
- name,
1197
- description: subAgent.description,
1198
- input: { type: "object" }
1234
+ type: "function",
1235
+ function: {
1236
+ name,
1237
+ description: subAgent.description,
1238
+ parameters: { type: "object" }
1239
+ }
1199
1240
  });
1200
1241
  }
1201
1242
  const defs = Array.from(definitions.values());