mslxdff 0.1.147 → 0.1.148

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mslxdff",
3
- "version": "0.1.147",
3
+ "version": "0.1.148",
4
4
  "description": "测试项目,请勿使用。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -54,6 +54,9 @@ export function responsesToChatBody(req = {}) {
54
54
  // 客户端重试/重放历史时会把同一 item 存两份),故按 id 保首个、丢后续——重复项无合法语义,删了不吃亏。
55
55
  let pendingReasoning = [];
56
56
  const seenReasoningIds = new Set();
57
+ // 同 call_id 的 function_call_output 重复出现 → 上游 400 "Duplicate function_call_output"
58
+ // (2026-09-23 实测,客户端重试/重放历史时会把同一工具结果存两份),故同样按 call_id 保首个、丢后续。
59
+ const seenToolResultIds = new Set();
57
60
  for (const it of items) {
58
61
  if (!it || typeof it !== "object") continue;
59
62
  if (it.type === "reasoning") {
@@ -81,6 +84,8 @@ export function responsesToChatBody(req = {}) {
81
84
  if (pendingReasoning.length) { msg.reasoning_items = pendingReasoning; pendingReasoning = []; }
82
85
  messages.push(msg);
83
86
  } else if (it.type === "function_call_output") {
87
+ const _tkey = String(it.call_id || "");
88
+ if (_tkey) { if (seenToolResultIds.has(_tkey)) continue; seenToolResultIds.add(_tkey); }
84
89
  messages.push({ role: "tool", tool_call_id: it.call_id || "", content: typeof it.output === "string" ? it.output : JSON.stringify(it.output ?? "") });
85
90
  }
86
91
  }
@@ -62,6 +62,9 @@ export function toModelPrompt(messages, { dropEncrypted = false } = {}) {
62
62
  // (上游报错原文即要求 Remove duplicate items)。这里按 itemId 保首个、丢后续,
63
63
  // 防客户端重放/其他生产者把同一加密 item 组装两次。
64
64
  const seenReasoningIds = new Set();
65
+ // 同 toolCallId 的 tool 结果重复出现 → 上游 400 "Duplicate function_call_output"
66
+ // (2026-09-23 实测:responses 入站虽已去重,此处仍加防线,防其他生产者直接组装 chat messages 重放)。
67
+ const seenToolResultIds = new Set();
65
68
  for (const m of Array.isArray(messages) ? messages : []) {
66
69
  if (!m || typeof m !== "object") continue;
67
70
  const role = String(m.role || "");
@@ -112,6 +115,8 @@ export function toModelPrompt(messages, { dropEncrypted = false } = {}) {
112
115
  }
113
116
  out.push({ role: "assistant", content: parts });
114
117
  } else if (role === "tool") {
118
+ const _tkey = String(m.tool_call_id || "");
119
+ if (_tkey) { if (seenToolResultIds.has(_tkey)) continue; seenToolResultIds.add(_tkey); }
115
120
  out.push({
116
121
  role: "tool",
117
122
  content: [{
@@ -24,6 +24,7 @@ export function diagnoseToolSequence(messages) {
24
24
  const list = Array.isArray(messages) ? messages : [];
25
25
  const issues = [];
26
26
  const open = new Map();
27
+ const seenResults = new Set();
27
28
  let calls = 0;
28
29
  let results = 0;
29
30
  const first = list[0];
@@ -50,8 +51,8 @@ export function diagnoseToolSequence(messages) {
50
51
  results++;
51
52
  const id = String(m.tool_call_id || "");
52
53
  if (!id) issues.push(`#${i} tool 结果空 id`);
53
- else if (!open.has(id)) issues.push(`#${i} 孤立结果:${id}`);
54
- else open.delete(id);
54
+ else if (seenResults.has(id)) issues.push(`#${i} 结果重复:${id}(上游按 call_id 查重会 400 Duplicate function_call_output,去重后只应出现一次)`);
55
+ else { seenResults.add(id); if (!open.has(id)) issues.push(`#${i} 孤立结果:${id}`); else open.delete(id); }
55
56
  } else if (open.size) {
56
57
  issues.push(`#${i} ${role} 打断{${[...open.keys()].join(",")}}`);
57
58
  }