@h1d3rone/claude-proxy 0.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/README.md +338 -0
- package/config_example.toml +11 -0
- package/package.json +38 -0
- package/src/cli.js +458 -0
- package/src/config.js +494 -0
- package/src/proxy/converters.js +533 -0
- package/src/proxy/server.js +271 -0
- package/src/services/client-config-manager.js +302 -0
- package/src/services/host-manager.js +369 -0
- package/src/utils.js +224 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
|
|
3
|
+
const CONSTANTS = {
|
|
4
|
+
ROLE_USER: "user",
|
|
5
|
+
ROLE_ASSISTANT: "assistant",
|
|
6
|
+
ROLE_SYSTEM: "system",
|
|
7
|
+
ROLE_TOOL: "tool",
|
|
8
|
+
CONTENT_TEXT: "text",
|
|
9
|
+
CONTENT_IMAGE: "image",
|
|
10
|
+
CONTENT_TOOL_USE: "tool_use",
|
|
11
|
+
CONTENT_TOOL_RESULT: "tool_result",
|
|
12
|
+
TOOL_FUNCTION: "function",
|
|
13
|
+
STOP_END_TURN: "end_turn",
|
|
14
|
+
STOP_MAX_TOKENS: "max_tokens",
|
|
15
|
+
STOP_TOOL_USE: "tool_use",
|
|
16
|
+
EVENT_MESSAGE_START: "message_start",
|
|
17
|
+
EVENT_MESSAGE_STOP: "message_stop",
|
|
18
|
+
EVENT_MESSAGE_DELTA: "message_delta",
|
|
19
|
+
EVENT_CONTENT_BLOCK_START: "content_block_start",
|
|
20
|
+
EVENT_CONTENT_BLOCK_STOP: "content_block_stop",
|
|
21
|
+
EVENT_CONTENT_BLOCK_DELTA: "content_block_delta",
|
|
22
|
+
EVENT_PING: "ping",
|
|
23
|
+
DELTA_TEXT: "text_delta",
|
|
24
|
+
DELTA_INPUT_JSON: "input_json_delta"
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function randomMessageId(prefix = "msg") {
|
|
28
|
+
return `${prefix}_${crypto.randomBytes(12).toString("hex")}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function mapClaudeModelToOpenAI(claudeModel, config) {
|
|
32
|
+
if (!claudeModel) {
|
|
33
|
+
return config.big_model;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const model = String(claudeModel);
|
|
37
|
+
if (
|
|
38
|
+
model.startsWith("gpt-") ||
|
|
39
|
+
model.startsWith("o1-") ||
|
|
40
|
+
model.startsWith("o3-") ||
|
|
41
|
+
model.startsWith("ep-") ||
|
|
42
|
+
model.startsWith("doubao-") ||
|
|
43
|
+
model.startsWith("deepseek-")
|
|
44
|
+
) {
|
|
45
|
+
return model;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const lower = model.toLowerCase();
|
|
49
|
+
if (lower.includes("haiku")) {
|
|
50
|
+
return config.small_model;
|
|
51
|
+
}
|
|
52
|
+
if (lower.includes("sonnet")) {
|
|
53
|
+
return config.middle_model;
|
|
54
|
+
}
|
|
55
|
+
if (lower.includes("opus")) {
|
|
56
|
+
return config.big_model;
|
|
57
|
+
}
|
|
58
|
+
return config.big_model;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function convertSystemPrompt(system) {
|
|
62
|
+
if (!system) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof system === "string") {
|
|
67
|
+
return system.trim() || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (Array.isArray(system)) {
|
|
71
|
+
const blocks = system
|
|
72
|
+
.filter((block) => block && block.type === CONSTANTS.CONTENT_TEXT)
|
|
73
|
+
.map((block) => block.text || "")
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
return blocks.join("\n\n").trim() || null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function convertClaudeUserMessage(message) {
|
|
82
|
+
if (typeof message.content === "string") {
|
|
83
|
+
return { role: CONSTANTS.ROLE_USER, content: message.content };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const content = [];
|
|
87
|
+
for (const block of message.content || []) {
|
|
88
|
+
if (!block || !block.type) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (block.type === CONSTANTS.CONTENT_TEXT) {
|
|
92
|
+
content.push({ type: "text", text: block.text || "" });
|
|
93
|
+
}
|
|
94
|
+
if (
|
|
95
|
+
block.type === CONSTANTS.CONTENT_IMAGE &&
|
|
96
|
+
block.source &&
|
|
97
|
+
block.source.type === "base64" &&
|
|
98
|
+
block.source.media_type &&
|
|
99
|
+
block.source.data
|
|
100
|
+
) {
|
|
101
|
+
content.push({
|
|
102
|
+
type: "image_url",
|
|
103
|
+
image_url: {
|
|
104
|
+
url: `data:${block.source.media_type};base64,${block.source.data}`
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (content.length === 1 && content[0].type === "text") {
|
|
111
|
+
return { role: CONSTANTS.ROLE_USER, content: content[0].text };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { role: CONSTANTS.ROLE_USER, content };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function convertClaudeAssistantMessage(message) {
|
|
118
|
+
if (typeof message.content === "string") {
|
|
119
|
+
return { role: CONSTANTS.ROLE_ASSISTANT, content: message.content };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const textParts = [];
|
|
123
|
+
const toolCalls = [];
|
|
124
|
+
|
|
125
|
+
for (const block of message.content || []) {
|
|
126
|
+
if (!block || !block.type) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (block.type === CONSTANTS.CONTENT_TEXT) {
|
|
130
|
+
textParts.push(block.text || "");
|
|
131
|
+
}
|
|
132
|
+
if (block.type === CONSTANTS.CONTENT_TOOL_USE) {
|
|
133
|
+
toolCalls.push({
|
|
134
|
+
id: block.id || randomMessageId("tool"),
|
|
135
|
+
type: CONSTANTS.TOOL_FUNCTION,
|
|
136
|
+
function: {
|
|
137
|
+
name: block.name || "",
|
|
138
|
+
arguments: JSON.stringify(block.input || {})
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const payload = {
|
|
145
|
+
role: CONSTANTS.ROLE_ASSISTANT,
|
|
146
|
+
content: textParts.length > 0 ? textParts.join("") : null
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (toolCalls.length > 0) {
|
|
150
|
+
payload.tool_calls = toolCalls;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return payload;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function normalizeToolResultContent(content) {
|
|
157
|
+
if (content == null) {
|
|
158
|
+
return "No content provided";
|
|
159
|
+
}
|
|
160
|
+
if (typeof content === "string") {
|
|
161
|
+
return content;
|
|
162
|
+
}
|
|
163
|
+
if (Array.isArray(content)) {
|
|
164
|
+
return content
|
|
165
|
+
.map((item) => {
|
|
166
|
+
if (typeof item === "string") {
|
|
167
|
+
return item;
|
|
168
|
+
}
|
|
169
|
+
if (item && typeof item === "object" && item.type === CONSTANTS.CONTENT_TEXT) {
|
|
170
|
+
return item.text || "";
|
|
171
|
+
}
|
|
172
|
+
return JSON.stringify(item);
|
|
173
|
+
})
|
|
174
|
+
.join("\n")
|
|
175
|
+
.trim();
|
|
176
|
+
}
|
|
177
|
+
return JSON.stringify(content);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function convertClaudeToolResults(message) {
|
|
181
|
+
const toolMessages = [];
|
|
182
|
+
for (const block of message.content || []) {
|
|
183
|
+
if (!block || block.type !== CONSTANTS.CONTENT_TOOL_RESULT) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
toolMessages.push({
|
|
187
|
+
role: CONSTANTS.ROLE_TOOL,
|
|
188
|
+
tool_call_id: block.tool_use_id,
|
|
189
|
+
content: normalizeToolResultContent(block.content)
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return toolMessages;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function convertClaudeToOpenAI(claudeRequest, config) {
|
|
196
|
+
const messages = [];
|
|
197
|
+
const systemPrompt = convertSystemPrompt(claudeRequest.system);
|
|
198
|
+
if (systemPrompt) {
|
|
199
|
+
messages.push({ role: CONSTANTS.ROLE_SYSTEM, content: systemPrompt });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (let index = 0; index < (claudeRequest.messages || []).length; index += 1) {
|
|
203
|
+
const message = claudeRequest.messages[index];
|
|
204
|
+
if (!message) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (message.role === CONSTANTS.ROLE_USER) {
|
|
209
|
+
messages.push(convertClaudeUserMessage(message));
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (message.role === CONSTANTS.ROLE_ASSISTANT) {
|
|
214
|
+
messages.push(convertClaudeAssistantMessage(message));
|
|
215
|
+
|
|
216
|
+
const nextMessage = claudeRequest.messages[index + 1];
|
|
217
|
+
const hasToolResult =
|
|
218
|
+
nextMessage &&
|
|
219
|
+
nextMessage.role === CONSTANTS.ROLE_USER &&
|
|
220
|
+
Array.isArray(nextMessage.content) &&
|
|
221
|
+
nextMessage.content.some((block) => block && block.type === CONSTANTS.CONTENT_TOOL_RESULT);
|
|
222
|
+
|
|
223
|
+
if (hasToolResult) {
|
|
224
|
+
index += 1;
|
|
225
|
+
messages.push(...convertClaudeToolResults(nextMessage));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const payload = {
|
|
231
|
+
model: mapClaudeModelToOpenAI(claudeRequest.model, config),
|
|
232
|
+
messages,
|
|
233
|
+
max_tokens: Math.max(1, Math.min(Number(claudeRequest.max_tokens || 4096), 128000)),
|
|
234
|
+
temperature: claudeRequest.temperature,
|
|
235
|
+
stream: Boolean(claudeRequest.stream)
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
if (Array.isArray(claudeRequest.stop_sequences) && claudeRequest.stop_sequences.length > 0) {
|
|
239
|
+
payload.stop = claudeRequest.stop_sequences;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (typeof claudeRequest.top_p === "number") {
|
|
243
|
+
payload.top_p = claudeRequest.top_p;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (Array.isArray(claudeRequest.tools) && claudeRequest.tools.length > 0) {
|
|
247
|
+
payload.tools = claudeRequest.tools
|
|
248
|
+
.filter((tool) => tool && tool.name)
|
|
249
|
+
.map((tool) => ({
|
|
250
|
+
type: CONSTANTS.TOOL_FUNCTION,
|
|
251
|
+
function: {
|
|
252
|
+
name: tool.name,
|
|
253
|
+
description: tool.description || "",
|
|
254
|
+
parameters: tool.input_schema || { type: "object", properties: {} }
|
|
255
|
+
}
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (claudeRequest.tool_choice && typeof claudeRequest.tool_choice === "object") {
|
|
260
|
+
const type = claudeRequest.tool_choice.type;
|
|
261
|
+
if (type === "tool" && claudeRequest.tool_choice.name) {
|
|
262
|
+
payload.tool_choice = {
|
|
263
|
+
type: CONSTANTS.TOOL_FUNCTION,
|
|
264
|
+
function: { name: claudeRequest.tool_choice.name }
|
|
265
|
+
};
|
|
266
|
+
} else {
|
|
267
|
+
payload.tool_choice = "auto";
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return payload;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function convertOpenAIToClaudeResponse(openaiResponse, originalRequest) {
|
|
275
|
+
const choice = (openaiResponse.choices || [])[0];
|
|
276
|
+
if (!choice) {
|
|
277
|
+
throw new Error("No choices returned by upstream API.");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const message = choice.message || {};
|
|
281
|
+
const content = [];
|
|
282
|
+
|
|
283
|
+
if (message.content != null) {
|
|
284
|
+
content.push({ type: CONSTANTS.CONTENT_TEXT, text: message.content });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
for (const toolCall of message.tool_calls || []) {
|
|
288
|
+
if (toolCall.type !== CONSTANTS.TOOL_FUNCTION) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
let parsedArguments;
|
|
292
|
+
try {
|
|
293
|
+
parsedArguments = JSON.parse(toolCall.function?.arguments || "{}");
|
|
294
|
+
} catch {
|
|
295
|
+
parsedArguments = { raw_arguments: toolCall.function?.arguments || "" };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
content.push({
|
|
299
|
+
type: CONSTANTS.CONTENT_TOOL_USE,
|
|
300
|
+
id: toolCall.id || randomMessageId("tool"),
|
|
301
|
+
name: toolCall.function?.name || "",
|
|
302
|
+
input: parsedArguments
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (content.length === 0) {
|
|
307
|
+
content.push({ type: CONSTANTS.CONTENT_TEXT, text: "" });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const finishReason = choice.finish_reason || "stop";
|
|
311
|
+
const stopReasonMap = {
|
|
312
|
+
stop: CONSTANTS.STOP_END_TURN,
|
|
313
|
+
length: CONSTANTS.STOP_MAX_TOKENS,
|
|
314
|
+
tool_calls: CONSTANTS.STOP_TOOL_USE,
|
|
315
|
+
function_call: CONSTANTS.STOP_TOOL_USE
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
id: openaiResponse.id || randomMessageId(),
|
|
320
|
+
type: "message",
|
|
321
|
+
role: CONSTANTS.ROLE_ASSISTANT,
|
|
322
|
+
model: originalRequest.model,
|
|
323
|
+
content,
|
|
324
|
+
stop_reason: stopReasonMap[finishReason] || CONSTANTS.STOP_END_TURN,
|
|
325
|
+
stop_sequence: null,
|
|
326
|
+
usage: {
|
|
327
|
+
input_tokens: openaiResponse.usage?.prompt_tokens || 0,
|
|
328
|
+
output_tokens: openaiResponse.usage?.completion_tokens || 0
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function writeSseEvent(response, event, data) {
|
|
334
|
+
response.write(`event: ${event}\n`);
|
|
335
|
+
response.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function streamOpenAiToClaude(upstreamResponse, response, originalRequest, requestAbortController) {
|
|
339
|
+
const messageId = randomMessageId();
|
|
340
|
+
const toolCalls = new Map();
|
|
341
|
+
let toolBlockCounter = 0;
|
|
342
|
+
let finalStopReason = CONSTANTS.STOP_END_TURN;
|
|
343
|
+
let usage = { input_tokens: 0, output_tokens: 0 };
|
|
344
|
+
|
|
345
|
+
writeSseEvent(response, CONSTANTS.EVENT_MESSAGE_START, {
|
|
346
|
+
type: CONSTANTS.EVENT_MESSAGE_START,
|
|
347
|
+
message: {
|
|
348
|
+
id: messageId,
|
|
349
|
+
type: "message",
|
|
350
|
+
role: CONSTANTS.ROLE_ASSISTANT,
|
|
351
|
+
model: originalRequest.model,
|
|
352
|
+
content: [],
|
|
353
|
+
stop_reason: null,
|
|
354
|
+
stop_sequence: null,
|
|
355
|
+
usage
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
writeSseEvent(response, CONSTANTS.EVENT_CONTENT_BLOCK_START, {
|
|
360
|
+
type: CONSTANTS.EVENT_CONTENT_BLOCK_START,
|
|
361
|
+
index: 0,
|
|
362
|
+
content_block: {
|
|
363
|
+
type: CONSTANTS.CONTENT_TEXT,
|
|
364
|
+
text: ""
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
writeSseEvent(response, CONSTANTS.EVENT_PING, {
|
|
369
|
+
type: CONSTANTS.EVENT_PING
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const decoder = new TextDecoder();
|
|
373
|
+
const reader = upstreamResponse.body.getReader();
|
|
374
|
+
let buffer = "";
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
while (true) {
|
|
378
|
+
const { done, value } = await reader.read();
|
|
379
|
+
if (done) {
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
buffer += decoder.decode(value, { stream: true });
|
|
384
|
+
const lines = buffer.split(/\r?\n/);
|
|
385
|
+
buffer = lines.pop() || "";
|
|
386
|
+
|
|
387
|
+
for (const line of lines) {
|
|
388
|
+
if (!line.startsWith("data: ")) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const chunkData = line.slice(6);
|
|
393
|
+
if (chunkData === "[DONE]") {
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let chunk;
|
|
398
|
+
try {
|
|
399
|
+
chunk = JSON.parse(chunkData);
|
|
400
|
+
} catch {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (chunk.usage) {
|
|
405
|
+
usage = {
|
|
406
|
+
input_tokens: chunk.usage.prompt_tokens || usage.input_tokens || 0,
|
|
407
|
+
output_tokens: chunk.usage.completion_tokens || usage.output_tokens || 0
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const choice = (chunk.choices || [])[0];
|
|
412
|
+
if (!choice) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const delta = choice.delta || {};
|
|
417
|
+
|
|
418
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
419
|
+
writeSseEvent(response, CONSTANTS.EVENT_CONTENT_BLOCK_DELTA, {
|
|
420
|
+
type: CONSTANTS.EVENT_CONTENT_BLOCK_DELTA,
|
|
421
|
+
index: 0,
|
|
422
|
+
delta: {
|
|
423
|
+
type: CONSTANTS.DELTA_TEXT,
|
|
424
|
+
text: delta.content
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
for (const toolDelta of delta.tool_calls || []) {
|
|
430
|
+
const index = toolDelta.index || 0;
|
|
431
|
+
if (!toolCalls.has(index)) {
|
|
432
|
+
toolCalls.set(index, {
|
|
433
|
+
id: null,
|
|
434
|
+
name: null,
|
|
435
|
+
started: false,
|
|
436
|
+
claudeIndex: null
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const tool = toolCalls.get(index);
|
|
441
|
+
if (toolDelta.id) {
|
|
442
|
+
tool.id = toolDelta.id;
|
|
443
|
+
}
|
|
444
|
+
if (toolDelta.function?.name) {
|
|
445
|
+
tool.name = toolDelta.function.name;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (!tool.started && tool.id && tool.name) {
|
|
449
|
+
toolBlockCounter += 1;
|
|
450
|
+
tool.started = true;
|
|
451
|
+
tool.claudeIndex = toolBlockCounter;
|
|
452
|
+
writeSseEvent(response, CONSTANTS.EVENT_CONTENT_BLOCK_START, {
|
|
453
|
+
type: CONSTANTS.EVENT_CONTENT_BLOCK_START,
|
|
454
|
+
index: tool.claudeIndex,
|
|
455
|
+
content_block: {
|
|
456
|
+
type: CONSTANTS.CONTENT_TOOL_USE,
|
|
457
|
+
id: tool.id,
|
|
458
|
+
name: tool.name,
|
|
459
|
+
input: {}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (tool.started && typeof toolDelta.function?.arguments === "string" && toolDelta.function.arguments) {
|
|
465
|
+
writeSseEvent(response, CONSTANTS.EVENT_CONTENT_BLOCK_DELTA, {
|
|
466
|
+
type: CONSTANTS.EVENT_CONTENT_BLOCK_DELTA,
|
|
467
|
+
index: tool.claudeIndex,
|
|
468
|
+
delta: {
|
|
469
|
+
type: CONSTANTS.DELTA_INPUT_JSON,
|
|
470
|
+
partial_json: toolDelta.function.arguments
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (choice.finish_reason) {
|
|
477
|
+
if (choice.finish_reason === "length") {
|
|
478
|
+
finalStopReason = CONSTANTS.STOP_MAX_TOKENS;
|
|
479
|
+
} else if (["tool_calls", "function_call"].includes(choice.finish_reason)) {
|
|
480
|
+
finalStopReason = CONSTANTS.STOP_TOOL_USE;
|
|
481
|
+
} else {
|
|
482
|
+
finalStopReason = CONSTANTS.STOP_END_TURN;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
} catch (error) {
|
|
488
|
+
requestAbortController.abort();
|
|
489
|
+
writeSseEvent(response, "error", {
|
|
490
|
+
type: "error",
|
|
491
|
+
error: {
|
|
492
|
+
type: "api_error",
|
|
493
|
+
message: error.message
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
writeSseEvent(response, CONSTANTS.EVENT_CONTENT_BLOCK_STOP, {
|
|
500
|
+
type: CONSTANTS.EVENT_CONTENT_BLOCK_STOP,
|
|
501
|
+
index: 0
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
for (const tool of toolCalls.values()) {
|
|
505
|
+
if (tool.started) {
|
|
506
|
+
writeSseEvent(response, CONSTANTS.EVENT_CONTENT_BLOCK_STOP, {
|
|
507
|
+
type: CONSTANTS.EVENT_CONTENT_BLOCK_STOP,
|
|
508
|
+
index: tool.claudeIndex
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
writeSseEvent(response, CONSTANTS.EVENT_MESSAGE_DELTA, {
|
|
514
|
+
type: CONSTANTS.EVENT_MESSAGE_DELTA,
|
|
515
|
+
delta: {
|
|
516
|
+
stop_reason: finalStopReason,
|
|
517
|
+
stop_sequence: null
|
|
518
|
+
},
|
|
519
|
+
usage
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
writeSseEvent(response, CONSTANTS.EVENT_MESSAGE_STOP, {
|
|
523
|
+
type: CONSTANTS.EVENT_MESSAGE_STOP
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
module.exports = {
|
|
528
|
+
CONSTANTS,
|
|
529
|
+
convertClaudeToOpenAI,
|
|
530
|
+
convertOpenAIToClaudeResponse,
|
|
531
|
+
mapClaudeModelToOpenAI,
|
|
532
|
+
streamOpenAiToClaude
|
|
533
|
+
};
|