mslxdff 0.1.102 → 0.1.104
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 +1 -1
- package/src/upstream-responses.js +130 -39
package/package.json
CHANGED
|
@@ -12,15 +12,54 @@ export function chatToResponsesBody(chatBody) {
|
|
|
12
12
|
const nonSystem = msgs.filter((m) => m.role !== "system");
|
|
13
13
|
const inputParts = nonSystem.map((m) => {
|
|
14
14
|
const c = m.content;
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
|
|
15
|
+
let base;
|
|
16
|
+
if (typeof c === "string") base = `${m.role}: ${c}`;
|
|
17
|
+
else if (Array.isArray(c)) base = `${m.role}: ${c.map((x) => x.text || x.content || "").join("")}`;
|
|
18
|
+
else base = `${m.role}: ${String(c || "")}`;
|
|
19
|
+
// 保留 tool_calls / tool 结果,避免多轮丢失
|
|
20
|
+
if (Array.isArray(m.tool_calls) && m.tool_calls.length) {
|
|
21
|
+
const tcStr = m.tool_calls.map((tc) => `${tc.function?.name || "tool"}(${tc.function?.arguments || ""})`).join("; ");
|
|
22
|
+
base += ` [tool_calls: ${tcStr}]`;
|
|
23
|
+
}
|
|
24
|
+
if (m.role === "tool" && m.tool_call_id) base += ` (call_id=${m.tool_call_id})`;
|
|
25
|
+
return base;
|
|
18
26
|
});
|
|
19
27
|
const input = inputParts.join("\n\n") || "hi";
|
|
20
|
-
const out = { model: chatBody.model, input, stream:
|
|
28
|
+
const out = { model: chatBody.model, input, stream: false };
|
|
21
29
|
if (system) out.instructions = system;
|
|
22
|
-
|
|
23
|
-
if (chatBody.
|
|
30
|
+
// responses 的 tools 形状为平铺 {type,name,description,parameters},而 chat 为 {type,function:{name,...}}
|
|
31
|
+
if (Array.isArray(chatBody.tools) && chatBody.tools.length) {
|
|
32
|
+
const mapped = chatBody.tools.map((t) => {
|
|
33
|
+
if (!t || typeof t !== "object") return null;
|
|
34
|
+
if (t.type === "function" && t.function && typeof t.function === "object") {
|
|
35
|
+
const fn = t.function;
|
|
36
|
+
// 去掉 responses 不支持的 strict 等字段,parameters 原样透传
|
|
37
|
+
const nt = { type: "function", name: fn.name, description: fn.description || undefined, parameters: fn.parameters || undefined };
|
|
38
|
+
// 清理 undefined
|
|
39
|
+
Object.keys(nt).forEach((k) => nt[k] === undefined && delete nt[k]);
|
|
40
|
+
return nt.name ? nt : null;
|
|
41
|
+
}
|
|
42
|
+
// 已是平铺形态或未知形态,透传但确保 name 存在,清理 strict
|
|
43
|
+
if (t.name) {
|
|
44
|
+
const { strict, ...rest } = t;
|
|
45
|
+
return rest;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}).filter(Boolean);
|
|
49
|
+
if (mapped.length) out.tools = mapped;
|
|
50
|
+
}
|
|
51
|
+
if (chatBody.tool_choice) {
|
|
52
|
+
const tc = chatBody.tool_choice;
|
|
53
|
+
// responses 仅支持 "auto"(实测 required/named 均 400),一律归一为 auto
|
|
54
|
+
if (typeof tc === "string") {
|
|
55
|
+
out.tool_choice = tc === "auto" ? "auto" : "auto";
|
|
56
|
+
} else if (tc && typeof tc === "object") {
|
|
57
|
+
if (tc.type === "auto" || tc.type === "required") out.tool_choice = "auto";
|
|
58
|
+
else if (tc.type === "function") out.tool_choice = "auto";
|
|
59
|
+
else if (tc.type) out.tool_choice = "auto";
|
|
60
|
+
else out.tool_choice = "auto";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
24
63
|
if (chatBody.temperature != null) out.temperature = chatBody.temperature;
|
|
25
64
|
if (chatBody.max_tokens != null) out.max_output_tokens = chatBody.max_tokens;
|
|
26
65
|
return out;
|
|
@@ -28,15 +67,22 @@ export function chatToResponsesBody(chatBody) {
|
|
|
28
67
|
|
|
29
68
|
export function responsesToChatJson(respJson) {
|
|
30
69
|
let text = "";
|
|
70
|
+
const toolCalls = [];
|
|
31
71
|
for (const item of respJson.output || []) {
|
|
32
72
|
if (item.type === "message" && item.role === "assistant") {
|
|
33
73
|
for (const c of item.content || []) {
|
|
34
74
|
if (c.type === "output_text") text += c.text || "";
|
|
35
75
|
else if (c.type === "text") text += c.text || "";
|
|
36
76
|
}
|
|
77
|
+
} else if (item.type === "function_call") {
|
|
78
|
+
toolCalls.push({
|
|
79
|
+
id: item.call_id || item.id || `call_${toolCalls.length}`,
|
|
80
|
+
type: "function",
|
|
81
|
+
function: { name: item.name || "", arguments: typeof item.arguments === "string" ? item.arguments : JSON.stringify(item.arguments || "") },
|
|
82
|
+
});
|
|
37
83
|
}
|
|
38
84
|
}
|
|
39
|
-
if (!text) {
|
|
85
|
+
if (!text && toolCalls.length === 0) {
|
|
40
86
|
for (const item of respJson.output || []) {
|
|
41
87
|
if (item.type === "message") {
|
|
42
88
|
const t = item.content?.[0]?.text;
|
|
@@ -44,12 +90,18 @@ export function responsesToChatJson(respJson) {
|
|
|
44
90
|
}
|
|
45
91
|
}
|
|
46
92
|
}
|
|
93
|
+
const message = { role: "assistant", content: text };
|
|
94
|
+
if (toolCalls.length) {
|
|
95
|
+
message.tool_calls = toolCalls;
|
|
96
|
+
// 有 tool_calls 时 content 可为 "",finish_reason 应为 tool_calls
|
|
97
|
+
}
|
|
98
|
+
const finish = toolCalls.length ? "tool_calls" : (respJson.status === "completed" ? "stop" : "length");
|
|
47
99
|
const chatJson = {
|
|
48
100
|
id: respJson.id || `resp_${Date.now()}`,
|
|
49
101
|
object: "chat.completion",
|
|
50
102
|
created: Math.floor((respJson.created_at || Date.now() / 1000)),
|
|
51
103
|
model: respJson.model,
|
|
52
|
-
choices: [{ index: 0, finish_reason:
|
|
104
|
+
choices: [{ index: 0, finish_reason: finish, message }],
|
|
53
105
|
usage: respJson.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
54
106
|
};
|
|
55
107
|
return chatJson;
|
|
@@ -77,6 +129,7 @@ export function reshapeResponsesSse(res, fallbackModel) {
|
|
|
77
129
|
let respModel = fallbackModel || "";
|
|
78
130
|
let created = Math.floor(Date.now() / 1000);
|
|
79
131
|
let hasSentRole = false;
|
|
132
|
+
const toolMap = new Map(); // output_index -> {idx, id, name}
|
|
80
133
|
|
|
81
134
|
function chatChunk(delta, finish) {
|
|
82
135
|
const id = respId || `resp_${Date.now()}`;
|
|
@@ -130,37 +183,75 @@ export function reshapeResponsesSse(res, fallbackModel) {
|
|
|
130
183
|
if (data.response?.created_at) created = Math.floor(data.response.created_at);
|
|
131
184
|
if (data.response?.id && !respId) respId = data.response.id;
|
|
132
185
|
// 关注 output_text.delta
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
186
|
+
if (curEvent === "response.output_text.delta" || data.type === "response.output_text.delta") {
|
|
187
|
+
const deltaText = data.delta || "";
|
|
188
|
+
if (deltaText) {
|
|
189
|
+
if (!hasSentRole) {
|
|
190
|
+
hasSentRole = true;
|
|
191
|
+
out += chatChunk({ role: "assistant" }, null);
|
|
192
|
+
}
|
|
193
|
+
out += chatChunk({ content: deltaText }, null);
|
|
194
|
+
}
|
|
195
|
+
} else if (curEvent === "response.completed" || data.type === "response.completed") {
|
|
196
|
+
const usage = data.response?.usage || null;
|
|
197
|
+
// 若有 tool_calls,finish 应为 tool_calls
|
|
198
|
+
const hasTools = toolMap.size > 0;
|
|
199
|
+
const finish = hasTools ? "tool_calls" : (data.response?.status === "completed" ? "stop" : null);
|
|
200
|
+
// 末帧带 usage
|
|
201
|
+
const id = respId || `resp_${Date.now()}`;
|
|
202
|
+
const payload = {
|
|
203
|
+
id,
|
|
204
|
+
object: "chat.completion.chunk",
|
|
205
|
+
created,
|
|
206
|
+
model: respModel,
|
|
207
|
+
choices: [{ index: 0, delta: {}, finish_reason: finish }],
|
|
208
|
+
usage: usage || undefined,
|
|
209
|
+
};
|
|
210
|
+
out += `data: ${JSON.stringify(payload)}\n\n`;
|
|
211
|
+
} else if (data.type === "response.output_item.added" && data.item?.type === "message") {
|
|
212
|
+
// message 开始,可发送 role
|
|
213
|
+
if (!hasSentRole) {
|
|
214
|
+
hasSentRole = true;
|
|
215
|
+
out += chatChunk({ role: "assistant" }, null);
|
|
216
|
+
}
|
|
217
|
+
} else if (data.type === "response.output_item.added" && data.item?.type === "function_call") {
|
|
218
|
+
const outIdx = Number(data.output_index ?? 1);
|
|
219
|
+
const toolIdx = Math.max(0, outIdx - 1);
|
|
220
|
+
const callId = data.item?.call_id || data.item?.id || "";
|
|
221
|
+
const name = data.item?.name || "";
|
|
222
|
+
toolMap.set(outIdx, { idx: toolIdx, id: callId, name });
|
|
223
|
+
if (!hasSentRole) {
|
|
224
|
+
hasSentRole = true;
|
|
225
|
+
out += chatChunk({ role: "assistant" }, null);
|
|
226
|
+
}
|
|
227
|
+
const tc = { index: toolIdx, id: callId, type: "function", function: { name, arguments: "" } };
|
|
228
|
+
// 清理空字符串,避免 undefined
|
|
229
|
+
if (!callId) delete tc.id;
|
|
230
|
+
if (!name) delete tc.function.name;
|
|
231
|
+
out += chatChunk({ tool_calls: [tc] }, null);
|
|
232
|
+
} else if (data.type === "response.function_call_arguments.delta") {
|
|
233
|
+
const outIdx = Number(data.output_index ?? 1);
|
|
234
|
+
const entry = toolMap.get(outIdx) || { idx: Math.max(0, outIdx - 1) };
|
|
235
|
+
const deltaArgs = data.delta || "";
|
|
236
|
+
if (deltaArgs) {
|
|
237
|
+
if (!hasSentRole) {
|
|
238
|
+
hasSentRole = true;
|
|
239
|
+
out += chatChunk({ role: "assistant" }, null);
|
|
240
|
+
}
|
|
241
|
+
out += chatChunk({ tool_calls: [{ index: entry.idx, function: { arguments: deltaArgs } }] }, null);
|
|
242
|
+
}
|
|
243
|
+
} else if (data.type === "response.function_call_arguments.done") {
|
|
244
|
+
const outIdx = Number(data.output_index ?? 1);
|
|
245
|
+
const entry = toolMap.get(outIdx) || { idx: Math.max(0, outIdx - 1) };
|
|
246
|
+
const args = data.arguments || "";
|
|
247
|
+
if (args && !toolMap.get(outIdx)?._done) {
|
|
248
|
+
// done 可能带全量,若未通过 delta 发送过,补发
|
|
249
|
+
// 已通过 delta 流式发送则忽略,避免重复
|
|
250
|
+
}
|
|
251
|
+
} else if (data.type === "response.output_item.done" && data.item?.type === "function_call") {
|
|
252
|
+
// 可忽略,已通过 added+delta 完整
|
|
253
|
+
}
|
|
254
|
+
// reasoning 加密块忽略
|
|
164
255
|
}
|
|
165
256
|
if (out) controller.enqueue(encoder.encode(out));
|
|
166
257
|
} catch {
|