mslxdff 0.1.117 → 0.1.118
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
|
@@ -4,6 +4,7 @@ import { appendRotationLog as defaultAppend } from "./rotation-log.js";
|
|
|
4
4
|
import { createTransport } from "../../transport/index.js";
|
|
5
5
|
import { reshapeWorkbuddySse } from "./reshape.js";
|
|
6
6
|
import { attemptOnceSdk } from "./sdk-chat.js";
|
|
7
|
+
import { sanitizeToolSequence } from "./sanitize-tools.js";
|
|
7
8
|
import { resolveEngineMode } from "../../upstream-engine/mode.js";
|
|
8
9
|
|
|
9
10
|
function buildAuthHeaders(key, auth) {
|
|
@@ -150,6 +151,11 @@ export function createChatService({
|
|
|
150
151
|
async function runChat(body, activeRing, opts = {}) {
|
|
151
152
|
const url = joinUrl(baseUrl, chatPath);
|
|
152
153
|
const t0 = nowMs(clock);
|
|
154
|
+
const clean = sanitizeToolSequence(body?.messages);
|
|
155
|
+
if (clean.droppedCalls || clean.droppedResults) {
|
|
156
|
+
try { console.error(`[workbuddy] tool-sequence sanitized: dropped ${clean.droppedCalls} call(s), ${clean.droppedResults} result(s), model=${body?.model || ""}`); } catch {}
|
|
157
|
+
body = { ...body, messages: clean.messages };
|
|
158
|
+
}
|
|
153
159
|
_t0Val = t0;
|
|
154
160
|
_modelVal = body?.model || "";
|
|
155
161
|
const preferredUid = opts?.workbuddyUid ? String(opts.workbuddyUid).trim() : "";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// workbuddy 上游对 tool 序列严格校验(400 code 11148 tool_call_sequence_broken):
|
|
2
|
+
// assistant.tool_calls 与后续 tool 结果必须按 id 一一配对,否则整单拒绝并提示开新会话。
|
|
3
|
+
// 历史上下文经压缩/编辑/跨模型切换后可能断裂(有调用无结果、结果孤立、id 漂移),
|
|
4
|
+
// AI SDK 转换层对缺失字段用占位(toolName "tool" / toolCallId "")进一步错配。
|
|
5
|
+
// 这里在 workbuddy 出口做防御性清洗:仅保留能成对出现的调用与结果,避免整单 400。
|
|
6
|
+
// 其他上游(opencode 等)宽容,不受影响——只挂在 workbuddy 链路。
|
|
7
|
+
export function sanitizeToolSequence(messages) {
|
|
8
|
+
const list = Array.isArray(messages) ? messages : [];
|
|
9
|
+
|
|
10
|
+
const callIds = new Set();
|
|
11
|
+
for (const m of list) {
|
|
12
|
+
if (m?.role === "assistant" && Array.isArray(m.tool_calls)) {
|
|
13
|
+
for (const tc of m.tool_calls) {
|
|
14
|
+
const id = String(tc?.id || "");
|
|
15
|
+
if (id) callIds.add(id);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const resultIds = new Set();
|
|
21
|
+
for (const m of list) {
|
|
22
|
+
if (m?.role === "tool") {
|
|
23
|
+
const id = String(m.tool_call_id || "");
|
|
24
|
+
if (id) resultIds.add(id);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const paired = new Set();
|
|
29
|
+
for (const id of callIds) if (resultIds.has(id)) paired.add(id);
|
|
30
|
+
|
|
31
|
+
let droppedCalls = 0;
|
|
32
|
+
let droppedResults = 0;
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const m of list) {
|
|
35
|
+
if (m?.role === "assistant" && Array.isArray(m.tool_calls) && m.tool_calls.length) {
|
|
36
|
+
const kept = m.tool_calls.filter((tc) => paired.has(String(tc?.id || "")));
|
|
37
|
+
droppedCalls += m.tool_calls.length - kept.length;
|
|
38
|
+
if (kept.length) {
|
|
39
|
+
out.push({ ...m, tool_calls: kept });
|
|
40
|
+
} else if (typeof m.content === "string" && m.content) {
|
|
41
|
+
const { tool_calls: _dropped, ...rest } = m;
|
|
42
|
+
out.push(rest);
|
|
43
|
+
}
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (m?.role === "tool") {
|
|
47
|
+
if (paired.has(String(m.tool_call_id || ""))) out.push(m);
|
|
48
|
+
else droppedResults++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
out.push(m);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { messages: out, droppedCalls, droppedResults };
|
|
55
|
+
}
|