codexmate 0.0.43 → 0.0.45
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 +2 -0
- package/README.zh.md +2 -0
- package/cli/claude-proxy.js +611 -14
- package/cli/update.js +77 -7
- package/cli.js +188 -21
- package/package.json +1 -1
- package/web-ui/app.js +36 -3
- package/web-ui/index.html +1 -0
- package/web-ui/logic.claude.mjs +65 -2
- package/web-ui/logic.runtime.mjs +0 -7
- package/web-ui/modules/app.computed.index.mjs +3 -1
- package/web-ui/modules/app.computed.main-tabs.mjs +3 -0
- package/web-ui/modules/app.computed.prompts.mjs +28 -0
- package/web-ui/modules/app.computed.session.mjs +23 -1
- package/web-ui/modules/app.methods.agents.mjs +50 -4
- package/web-ui/modules/app.methods.claude-config.mjs +28 -12
- package/web-ui/modules/app.methods.index.mjs +1 -1
- package/web-ui/modules/app.methods.install.mjs +129 -1
- package/web-ui/modules/app.methods.navigation.mjs +2 -1
- package/web-ui/modules/app.methods.session-actions.mjs +17 -2
- package/web-ui/modules/app.methods.session-timeline.mjs +0 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +26 -3
- package/web-ui/modules/i18n/locales/en.mjs +42 -5
- package/web-ui/modules/i18n/locales/ja.mjs +42 -5
- package/web-ui/modules/i18n/locales/vi.mjs +51 -0
- package/web-ui/modules/i18n/locales/zh.mjs +42 -5
- package/web-ui/partials/index/layout-footer.html +1 -1
- package/web-ui/partials/index/layout-header.html +64 -0
- package/web-ui/partials/index/modal-config-template-agents.html +12 -13
- package/web-ui/partials/index/modals-basic.html +18 -1
- package/web-ui/partials/index/panel-config-claude.html +4 -7
- package/web-ui/partials/index/panel-config-codex.html +2 -6
- package/web-ui/partials/index/panel-prompts.html +100 -0
- package/web-ui/partials/index/panel-sessions.html +30 -10
- package/web-ui/partials/index/panel-usage.html +34 -18
- package/web-ui/res/web-ui-render.precompiled.js +579 -149
- package/web-ui/styles/controls-forms.css +5 -5
- package/web-ui/styles/layout-shell.css +145 -0
- package/web-ui/styles/modals-core.css +162 -0
- package/web-ui/styles/responsive.css +77 -5
- package/web-ui/styles/sessions-toolbar-trash.css +45 -10
- package/web-ui/styles/sessions-usage.css +31 -2
package/cli/claude-proxy.js
CHANGED
|
@@ -84,6 +84,40 @@ function stringifyAnthropicToolResultContent(content) {
|
|
|
84
84
|
return safeJsonStringify(content);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function buildOpenAIImageUrlFromAnthropicSource(source) {
|
|
88
|
+
if (!source || typeof source !== 'object') return '';
|
|
89
|
+
if (source.type === 'base64' && typeof source.data === 'string' && source.data.trim()) {
|
|
90
|
+
const mediaType = typeof source.media_type === 'string' && source.media_type.trim()
|
|
91
|
+
? source.media_type.trim()
|
|
92
|
+
: 'image/png';
|
|
93
|
+
return `data:${mediaType};base64,${source.data.trim()}`;
|
|
94
|
+
}
|
|
95
|
+
if (source.type === 'url' && typeof source.url === 'string' && source.url.trim()) {
|
|
96
|
+
return source.url.trim();
|
|
97
|
+
}
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function collectAnthropicImageBase64(source) {
|
|
102
|
+
if (!source || typeof source !== 'object') return '';
|
|
103
|
+
if (source.type === 'base64' && typeof source.data === 'string' && source.data.trim()) {
|
|
104
|
+
return source.data.trim();
|
|
105
|
+
}
|
|
106
|
+
const url = buildOpenAIImageUrlFromAnthropicSource(source);
|
|
107
|
+
const match = url ? url.match(/^data:[^;,]+;base64,(.+)$/i) : null;
|
|
108
|
+
return match && match[1] ? match[1] : '';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isDroppableAnthropicBridgeBlock(block) {
|
|
112
|
+
const type = block && typeof block.type === 'string' ? block.type : '';
|
|
113
|
+
return type === 'thinking' || type === 'document';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isDroppableAnthropicOllamaBlock(block) {
|
|
117
|
+
const type = block && typeof block.type === 'string' ? block.type : '';
|
|
118
|
+
return type === 'thinking' || type === 'document' || type === 'video';
|
|
119
|
+
}
|
|
120
|
+
|
|
87
121
|
function appendAnthropicMessageToResponsesInput(target, message) {
|
|
88
122
|
if (!message || typeof message !== 'object') return;
|
|
89
123
|
const roleRaw = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
@@ -103,6 +137,13 @@ function appendAnthropicMessageToResponsesInput(target, message) {
|
|
|
103
137
|
buffered.push({ type: textType, text: block.text });
|
|
104
138
|
continue;
|
|
105
139
|
}
|
|
140
|
+
if (block.type === 'image' && role === 'user') {
|
|
141
|
+
const imageUrl = buildOpenAIImageUrlFromAnthropicSource(block.source);
|
|
142
|
+
if (imageUrl) {
|
|
143
|
+
buffered.push({ type: 'input_image', image_url: imageUrl });
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
106
147
|
if (block.type === 'tool_use' && typeof block.name === 'string' && block.name.trim()) {
|
|
107
148
|
flushBuffered();
|
|
108
149
|
target.push({
|
|
@@ -124,6 +165,9 @@ function appendAnthropicMessageToResponsesInput(target, message) {
|
|
|
124
165
|
});
|
|
125
166
|
continue;
|
|
126
167
|
}
|
|
168
|
+
if (isDroppableAnthropicBridgeBlock(block)) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
127
171
|
buffered.push({
|
|
128
172
|
type: textType,
|
|
129
173
|
text: `[unsupported anthropic block: ${typeof block.type === 'string' ? block.type : 'unknown'}]`
|
|
@@ -133,6 +177,23 @@ function appendAnthropicMessageToResponsesInput(target, message) {
|
|
|
133
177
|
flushBuffered();
|
|
134
178
|
}
|
|
135
179
|
|
|
180
|
+
function mapAnthropicToolChoiceToChat(toolChoice) {
|
|
181
|
+
if (!toolChoice) return undefined;
|
|
182
|
+
if (typeof toolChoice === 'string') {
|
|
183
|
+
if (toolChoice === 'auto' || toolChoice === 'none') return toolChoice;
|
|
184
|
+
if (toolChoice === 'any') return 'required';
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
if (!toolChoice || typeof toolChoice !== 'object') return undefined;
|
|
188
|
+
const type = typeof toolChoice.type === 'string' ? toolChoice.type.trim().toLowerCase() : '';
|
|
189
|
+
if (type === 'auto' || type === 'none') return type;
|
|
190
|
+
if (type === 'any') return 'required';
|
|
191
|
+
if (type === 'tool' && typeof toolChoice.name === 'string' && toolChoice.name.trim()) {
|
|
192
|
+
return { type: 'function', function: { name: toolChoice.name.trim() } };
|
|
193
|
+
}
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
136
197
|
function mapAnthropicToolChoiceToResponses(toolChoice) {
|
|
137
198
|
if (!toolChoice) return undefined;
|
|
138
199
|
if (typeof toolChoice === 'string') {
|
|
@@ -218,6 +279,277 @@ function buildBuiltinClaudeResponsesRequest(payload = {}) {
|
|
|
218
279
|
return requestBody;
|
|
219
280
|
}
|
|
220
281
|
|
|
282
|
+
function appendAnthropicMessageToChatMessages(target, message) {
|
|
283
|
+
if (!message || typeof message !== 'object') return;
|
|
284
|
+
const roleRaw = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
285
|
+
const role = roleRaw === 'assistant' ? 'assistant' : 'user';
|
|
286
|
+
let textParts = [];
|
|
287
|
+
let contentParts = [];
|
|
288
|
+
const toolCalls = [];
|
|
289
|
+
|
|
290
|
+
const flushTextPartsToContentParts = () => {
|
|
291
|
+
const content = textParts.join('\n\n').trim();
|
|
292
|
+
textParts = [];
|
|
293
|
+
if (content) {
|
|
294
|
+
contentParts.push({ type: 'text', text: content });
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const buildContent = () => {
|
|
299
|
+
if (contentParts.length) {
|
|
300
|
+
flushTextPartsToContentParts();
|
|
301
|
+
if (contentParts.length === 1 && contentParts[0].type === 'text') {
|
|
302
|
+
return contentParts[0].text;
|
|
303
|
+
}
|
|
304
|
+
return contentParts;
|
|
305
|
+
}
|
|
306
|
+
return textParts.join('\n\n').trim();
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const flushText = () => {
|
|
310
|
+
const content = buildContent();
|
|
311
|
+
textParts = [];
|
|
312
|
+
contentParts = [];
|
|
313
|
+
if (!content || (Array.isArray(content) && !content.length)) return;
|
|
314
|
+
target.push({ role, content });
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
for (const block of normalizeAnthropicContentBlocks(message.content)) {
|
|
318
|
+
if (!block || typeof block !== 'object') continue;
|
|
319
|
+
if (block.type === 'text' && typeof block.text === 'string' && block.text) {
|
|
320
|
+
textParts.push(block.text);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (block.type === 'image' && role === 'user') {
|
|
324
|
+
flushTextPartsToContentParts();
|
|
325
|
+
const imageUrl = buildOpenAIImageUrlFromAnthropicSource(block.source);
|
|
326
|
+
if (imageUrl) {
|
|
327
|
+
contentParts.push({ type: 'image_url', image_url: { url: imageUrl } });
|
|
328
|
+
}
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (isDroppableAnthropicBridgeBlock(block)) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (block.type === 'tool_use' && role === 'assistant' && typeof block.name === 'string' && block.name.trim()) {
|
|
335
|
+
toolCalls.push({
|
|
336
|
+
id: typeof block.id === 'string' && block.id.trim()
|
|
337
|
+
? block.id.trim()
|
|
338
|
+
: `call_${crypto.randomBytes(8).toString('hex')}`,
|
|
339
|
+
type: 'function',
|
|
340
|
+
function: {
|
|
341
|
+
name: block.name.trim(),
|
|
342
|
+
arguments: safeJsonStringify(block.input && typeof block.input === 'object' ? block.input : {})
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (block.type === 'tool_result' && typeof block.tool_use_id === 'string' && block.tool_use_id.trim()) {
|
|
348
|
+
flushText();
|
|
349
|
+
target.push({
|
|
350
|
+
role: 'tool',
|
|
351
|
+
tool_call_id: block.tool_use_id.trim(),
|
|
352
|
+
content: stringifyAnthropicToolResultContent(block.content)
|
|
353
|
+
});
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
textParts.push(`[unsupported anthropic block: ${typeof block.type === 'string' ? block.type : 'unknown'}]`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (role === 'assistant' && toolCalls.length) {
|
|
360
|
+
const content = buildContent();
|
|
361
|
+
target.push({
|
|
362
|
+
role: 'assistant',
|
|
363
|
+
content: content || null,
|
|
364
|
+
tool_calls: toolCalls
|
|
365
|
+
});
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
flushText();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function buildBuiltinClaudeChatCompletionsRequest(payload = {}) {
|
|
372
|
+
const model = typeof payload.model === 'string' ? payload.model.trim() : '';
|
|
373
|
+
if (!model) {
|
|
374
|
+
throw new Error('Anthropic messages 请求缺少 model');
|
|
375
|
+
}
|
|
376
|
+
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
377
|
+
if (!messages.length) {
|
|
378
|
+
throw new Error('Anthropic messages 请求缺少 messages');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const requestBody = { model, messages: [] };
|
|
382
|
+
const systemText = collectAnthropicTextContent(payload.system);
|
|
383
|
+
if (systemText) {
|
|
384
|
+
requestBody.messages.push({ role: 'system', content: systemText });
|
|
385
|
+
}
|
|
386
|
+
for (const message of messages) {
|
|
387
|
+
appendAnthropicMessageToChatMessages(requestBody.messages, message);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const maxTokens = parseInt(String(payload.max_tokens), 10);
|
|
391
|
+
if (Number.isFinite(maxTokens) && maxTokens > 0) {
|
|
392
|
+
requestBody.max_tokens = maxTokens;
|
|
393
|
+
}
|
|
394
|
+
if (Number.isFinite(payload.temperature)) {
|
|
395
|
+
requestBody.temperature = Number(payload.temperature);
|
|
396
|
+
}
|
|
397
|
+
if (Number.isFinite(payload.top_p)) {
|
|
398
|
+
requestBody.top_p = Number(payload.top_p);
|
|
399
|
+
}
|
|
400
|
+
if (Array.isArray(payload.stop_sequences) && payload.stop_sequences.length) {
|
|
401
|
+
const stop = payload.stop_sequences.filter((item) => typeof item === 'string' && item.trim());
|
|
402
|
+
if (stop.length) requestBody.stop = stop;
|
|
403
|
+
}
|
|
404
|
+
if (Array.isArray(payload.tools) && payload.tools.length) {
|
|
405
|
+
requestBody.tools = payload.tools
|
|
406
|
+
.map((tool) => {
|
|
407
|
+
if (!tool || typeof tool !== 'object') return null;
|
|
408
|
+
const name = typeof tool.name === 'string' ? tool.name.trim() : '';
|
|
409
|
+
if (!name) return null;
|
|
410
|
+
return {
|
|
411
|
+
type: 'function',
|
|
412
|
+
function: {
|
|
413
|
+
name,
|
|
414
|
+
description: typeof tool.description === 'string' ? tool.description : '',
|
|
415
|
+
parameters: isPlainObject(tool.input_schema) ? tool.input_schema : { type: 'object', properties: {} }
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
})
|
|
419
|
+
.filter(Boolean);
|
|
420
|
+
if (!requestBody.tools.length) delete requestBody.tools;
|
|
421
|
+
}
|
|
422
|
+
const toolChoice = mapAnthropicToolChoiceToChat(payload.tool_choice);
|
|
423
|
+
if (toolChoice !== undefined) {
|
|
424
|
+
requestBody.tool_choice = toolChoice;
|
|
425
|
+
}
|
|
426
|
+
requestBody.stream = false;
|
|
427
|
+
return requestBody;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
function appendAnthropicMessageToOllamaMessages(target, message) {
|
|
433
|
+
if (!message || typeof message !== 'object') return;
|
|
434
|
+
const roleRaw = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
435
|
+
const role = roleRaw === 'assistant' ? 'assistant' : 'user';
|
|
436
|
+
let textParts = [];
|
|
437
|
+
let images = [];
|
|
438
|
+
const toolCalls = [];
|
|
439
|
+
|
|
440
|
+
const flushText = () => {
|
|
441
|
+
const content = textParts.join('\n\n').trim();
|
|
442
|
+
textParts = [];
|
|
443
|
+
if (!content && !images.length) return;
|
|
444
|
+
const msg = { role, content };
|
|
445
|
+
if (images.length) msg.images = images;
|
|
446
|
+
target.push(msg);
|
|
447
|
+
images = [];
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
for (const block of normalizeAnthropicContentBlocks(message.content)) {
|
|
451
|
+
if (!block || typeof block !== 'object') continue;
|
|
452
|
+
if (block.type === 'text' && typeof block.text === 'string' && block.text) {
|
|
453
|
+
textParts.push(block.text);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (block.type === 'image' && role === 'user') {
|
|
457
|
+
const image = collectAnthropicImageBase64(block.source);
|
|
458
|
+
if (image) images.push(image);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (isDroppableAnthropicOllamaBlock(block)) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (block.type === 'tool_use' && role === 'assistant' && typeof block.name === 'string' && block.name.trim()) {
|
|
465
|
+
toolCalls.push({
|
|
466
|
+
function: {
|
|
467
|
+
name: block.name.trim(),
|
|
468
|
+
arguments: block.input && typeof block.input === 'object' ? block.input : {}
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (block.type === 'tool_result' && typeof block.tool_use_id === 'string' && block.tool_use_id.trim()) {
|
|
474
|
+
flushText();
|
|
475
|
+
target.push({
|
|
476
|
+
role: 'tool',
|
|
477
|
+
content: stringifyAnthropicToolResultContent(block.content),
|
|
478
|
+
tool_call_id: block.tool_use_id.trim()
|
|
479
|
+
});
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
textParts.push(`[unsupported anthropic block: ${typeof block.type === 'string' ? block.type : 'unknown'}]`);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (role === 'assistant' && toolCalls.length) {
|
|
486
|
+
const content = textParts.join('\n\n').trim();
|
|
487
|
+
const msg = { role: 'assistant', content, tool_calls: toolCalls };
|
|
488
|
+
target.push(msg);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
flushText();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function buildBuiltinClaudeOllamaChatRequest(payload = {}) {
|
|
495
|
+
const model = typeof payload.model === 'string' ? payload.model.trim() : '';
|
|
496
|
+
if (!model) {
|
|
497
|
+
throw new Error('Anthropic messages 请求缺少 model');
|
|
498
|
+
}
|
|
499
|
+
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
500
|
+
if (!messages.length) {
|
|
501
|
+
throw new Error('Anthropic messages 请求缺少 messages');
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const requestBody = { model, messages: [], stream: false };
|
|
505
|
+
const systemText = collectAnthropicTextContent(payload.system);
|
|
506
|
+
if (systemText) {
|
|
507
|
+
requestBody.messages.push({ role: 'system', content: systemText });
|
|
508
|
+
}
|
|
509
|
+
for (const message of messages) {
|
|
510
|
+
appendAnthropicMessageToOllamaMessages(requestBody.messages, message);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const options = {};
|
|
514
|
+
const maxTokens = parseInt(String(payload.max_tokens), 10);
|
|
515
|
+
if (Number.isFinite(maxTokens) && maxTokens > 0) options.num_predict = maxTokens;
|
|
516
|
+
if (Number.isFinite(payload.temperature)) options.temperature = Number(payload.temperature);
|
|
517
|
+
if (Number.isFinite(payload.top_p)) options.top_p = Number(payload.top_p);
|
|
518
|
+
if (Array.isArray(payload.stop_sequences) && payload.stop_sequences.length) {
|
|
519
|
+
const stop = payload.stop_sequences.filter((item) => typeof item === 'string' && item.trim());
|
|
520
|
+
if (stop.length) options.stop = stop;
|
|
521
|
+
}
|
|
522
|
+
if (Object.keys(options).length) requestBody.options = options;
|
|
523
|
+
|
|
524
|
+
if (isPlainObject(payload.thinking)) {
|
|
525
|
+
const thinkingType = typeof payload.thinking.type === 'string'
|
|
526
|
+
? payload.thinking.type.trim().toLowerCase()
|
|
527
|
+
: '';
|
|
528
|
+
if (thinkingType === 'enabled') requestBody.think = true;
|
|
529
|
+
if (thinkingType === 'disabled') requestBody.think = false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (Array.isArray(payload.tools) && payload.tools.length) {
|
|
533
|
+
requestBody.tools = payload.tools
|
|
534
|
+
.map((tool) => {
|
|
535
|
+
if (!tool || typeof tool !== 'object') return null;
|
|
536
|
+
const name = typeof tool.name === 'string' ? tool.name.trim() : '';
|
|
537
|
+
if (!name) return null;
|
|
538
|
+
return {
|
|
539
|
+
type: 'function',
|
|
540
|
+
function: {
|
|
541
|
+
name,
|
|
542
|
+
description: typeof tool.description === 'string' ? tool.description : '',
|
|
543
|
+
parameters: isPlainObject(tool.input_schema) ? tool.input_schema : { type: 'object', properties: {} }
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
})
|
|
547
|
+
.filter(Boolean);
|
|
548
|
+
if (!requestBody.tools.length) delete requestBody.tools;
|
|
549
|
+
}
|
|
550
|
+
return requestBody;
|
|
551
|
+
}
|
|
552
|
+
|
|
221
553
|
function parseJsonObjectLoose(value) {
|
|
222
554
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
223
555
|
return value;
|
|
@@ -296,6 +628,133 @@ function buildAnthropicStopReasonFromResponses(payload, content) {
|
|
|
296
628
|
return 'end_turn';
|
|
297
629
|
}
|
|
298
630
|
|
|
631
|
+
function buildAnthropicUsageFromChatCompletion(payload) {
|
|
632
|
+
const usage = payload && payload.usage && typeof payload.usage === 'object' ? payload.usage : {};
|
|
633
|
+
return {
|
|
634
|
+
input_tokens: readResponsesUsageValue(usage.prompt_tokens),
|
|
635
|
+
output_tokens: readResponsesUsageValue(usage.completion_tokens)
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function normalizeChatMessageContentText(content) {
|
|
640
|
+
if (typeof content === 'string') return content;
|
|
641
|
+
if (Array.isArray(content)) {
|
|
642
|
+
return content.map((item) => {
|
|
643
|
+
if (typeof item === 'string') return item;
|
|
644
|
+
if (item && typeof item === 'object' && typeof item.text === 'string') return item.text;
|
|
645
|
+
return '';
|
|
646
|
+
}).filter(Boolean).join('\n\n');
|
|
647
|
+
}
|
|
648
|
+
return '';
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function buildAnthropicStopReasonFromChatChoice(choice, content) {
|
|
652
|
+
if (Array.isArray(content) && content.some((item) => item && item.type === 'tool_use')) {
|
|
653
|
+
return 'tool_use';
|
|
654
|
+
}
|
|
655
|
+
const finishReason = choice && typeof choice.finish_reason === 'string' ? choice.finish_reason : '';
|
|
656
|
+
if (finishReason === 'length') return 'max_tokens';
|
|
657
|
+
if (finishReason === 'tool_calls' || finishReason === 'function_call') return 'tool_use';
|
|
658
|
+
return 'end_turn';
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function buildAnthropicMessageFromChatCompletion(payload, requestPayload = {}) {
|
|
662
|
+
const choices = Array.isArray(payload && payload.choices) ? payload.choices : [];
|
|
663
|
+
const choice = choices.find((item) => item && item.message) || choices[0] || {};
|
|
664
|
+
const chatMessage = choice && choice.message && typeof choice.message === 'object' ? choice.message : {};
|
|
665
|
+
const content = [];
|
|
666
|
+
const text = normalizeChatMessageContentText(chatMessage.content);
|
|
667
|
+
if (text) {
|
|
668
|
+
content.push({ type: 'text', text });
|
|
669
|
+
}
|
|
670
|
+
const toolCalls = Array.isArray(chatMessage.tool_calls) ? chatMessage.tool_calls : [];
|
|
671
|
+
for (const call of toolCalls) {
|
|
672
|
+
if (!call || typeof call !== 'object') continue;
|
|
673
|
+
const fn = call.function && typeof call.function === 'object' ? call.function : {};
|
|
674
|
+
const name = typeof fn.name === 'string' ? fn.name : '';
|
|
675
|
+
if (!name) continue;
|
|
676
|
+
content.push({
|
|
677
|
+
type: 'tool_use',
|
|
678
|
+
id: typeof call.id === 'string' && call.id.trim()
|
|
679
|
+
? call.id.trim()
|
|
680
|
+
: `toolu_${crypto.randomBytes(8).toString('hex')}`,
|
|
681
|
+
name,
|
|
682
|
+
input: parseJsonObjectLoose(fn.arguments)
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
if (!content.length) {
|
|
686
|
+
const fallbackText = extractModelResponseText(payload);
|
|
687
|
+
if (fallbackText) content.push({ type: 'text', text: fallbackText });
|
|
688
|
+
}
|
|
689
|
+
const usage = buildAnthropicUsageFromChatCompletion(payload);
|
|
690
|
+
return {
|
|
691
|
+
id: typeof payload.id === 'string' && payload.id.trim()
|
|
692
|
+
? payload.id.trim()
|
|
693
|
+
: `msg_${crypto.randomBytes(8).toString('hex')}`,
|
|
694
|
+
type: 'message',
|
|
695
|
+
role: 'assistant',
|
|
696
|
+
model: typeof payload.model === 'string' && payload.model.trim()
|
|
697
|
+
? payload.model.trim()
|
|
698
|
+
: (typeof requestPayload.model === 'string' ? requestPayload.model : ''),
|
|
699
|
+
content,
|
|
700
|
+
stop_reason: buildAnthropicStopReasonFromChatChoice(choice, content),
|
|
701
|
+
stop_sequence: null,
|
|
702
|
+
usage
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
function buildAnthropicMessageFromOllamaChat(payload, requestPayload = {}) {
|
|
708
|
+
const ollamaMessage = payload && payload.message && typeof payload.message === 'object' ? payload.message : {};
|
|
709
|
+
const content = [];
|
|
710
|
+
if (typeof ollamaMessage.thinking === 'string' && ollamaMessage.thinking) {
|
|
711
|
+
content.push({ type: 'thinking', thinking: ollamaMessage.thinking });
|
|
712
|
+
}
|
|
713
|
+
if (typeof ollamaMessage.content === 'string' && ollamaMessage.content) {
|
|
714
|
+
content.push({ type: 'text', text: ollamaMessage.content });
|
|
715
|
+
}
|
|
716
|
+
const toolCalls = Array.isArray(ollamaMessage.tool_calls) ? ollamaMessage.tool_calls : [];
|
|
717
|
+
for (const call of toolCalls) {
|
|
718
|
+
if (!call || typeof call !== 'object') continue;
|
|
719
|
+
const fn = call.function && typeof call.function === 'object' ? call.function : {};
|
|
720
|
+
const name = typeof fn.name === 'string' ? fn.name : '';
|
|
721
|
+
if (!name) continue;
|
|
722
|
+
content.push({
|
|
723
|
+
type: 'tool_use',
|
|
724
|
+
id: typeof call.id === 'string' && call.id.trim()
|
|
725
|
+
? call.id.trim()
|
|
726
|
+
: `toolu_${crypto.randomBytes(8).toString('hex')}`,
|
|
727
|
+
name,
|
|
728
|
+
input: parseJsonObjectLoose(fn.arguments)
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
if (!content.length) {
|
|
732
|
+
const fallbackText = extractModelResponseText(payload);
|
|
733
|
+
if (fallbackText) content.push({ type: 'text', text: fallbackText });
|
|
734
|
+
}
|
|
735
|
+
const usage = {
|
|
736
|
+
input_tokens: readResponsesUsageValue(payload && payload.prompt_eval_count),
|
|
737
|
+
output_tokens: readResponsesUsageValue(payload && payload.eval_count)
|
|
738
|
+
};
|
|
739
|
+
const doneReason = payload && typeof payload.done_reason === 'string' ? payload.done_reason : '';
|
|
740
|
+
return {
|
|
741
|
+
id: typeof payload.id === 'string' && payload.id.trim()
|
|
742
|
+
? payload.id.trim()
|
|
743
|
+
: `msg_${crypto.randomBytes(8).toString('hex')}`,
|
|
744
|
+
type: 'message',
|
|
745
|
+
role: 'assistant',
|
|
746
|
+
model: typeof payload.model === 'string' && payload.model.trim()
|
|
747
|
+
? payload.model.trim()
|
|
748
|
+
: (typeof requestPayload.model === 'string' ? requestPayload.model : ''),
|
|
749
|
+
content,
|
|
750
|
+
stop_reason: Array.isArray(content) && content.some((item) => item && item.type === 'tool_use')
|
|
751
|
+
? 'tool_use'
|
|
752
|
+
: (doneReason === 'length' || doneReason === 'max_tokens' ? 'max_tokens' : 'end_turn'),
|
|
753
|
+
stop_sequence: null,
|
|
754
|
+
usage
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
299
758
|
function buildAnthropicMessageFromResponses(payload, requestPayload = {}) {
|
|
300
759
|
const content = collectAnthropicContentFromResponsesOutput(payload);
|
|
301
760
|
const usage = buildAnthropicUsageFromResponses(payload);
|
|
@@ -360,6 +819,28 @@ function buildAnthropicStreamEvents(message) {
|
|
|
360
819
|
events.push({ event: 'content_block_stop', data: { type: 'content_block_stop', index } });
|
|
361
820
|
return;
|
|
362
821
|
}
|
|
822
|
+
if (block.type === 'thinking') {
|
|
823
|
+
events.push({
|
|
824
|
+
event: 'content_block_start',
|
|
825
|
+
data: {
|
|
826
|
+
type: 'content_block_start',
|
|
827
|
+
index,
|
|
828
|
+
content_block: { type: 'thinking', thinking: '' }
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
if (typeof block.thinking === 'string' && block.thinking) {
|
|
832
|
+
events.push({
|
|
833
|
+
event: 'content_block_delta',
|
|
834
|
+
data: {
|
|
835
|
+
type: 'content_block_delta',
|
|
836
|
+
index,
|
|
837
|
+
delta: { type: 'thinking_delta', thinking: block.thinking }
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
events.push({ event: 'content_block_stop', data: { type: 'content_block_stop', index } });
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
363
844
|
if (block.type === 'tool_use') {
|
|
364
845
|
events.push({
|
|
365
846
|
event: 'content_block_start',
|
|
@@ -423,6 +904,15 @@ function buildAnthropicModelsPayload(upstreamPayload) {
|
|
|
423
904
|
};
|
|
424
905
|
}
|
|
425
906
|
|
|
907
|
+
function joinBuiltinClaudeProxyUpstreamUrl(baseUrl, pathSuffix) {
|
|
908
|
+
const suffix = typeof pathSuffix === 'string' ? pathSuffix.replace(/^\/+/, '') : '';
|
|
909
|
+
if (suffix === 'api/tags' || suffix === 'api/chat') {
|
|
910
|
+
const normalized = normalizeBaseUrl(baseUrl);
|
|
911
|
+
return normalized ? `${normalized}/${suffix}` : '';
|
|
912
|
+
}
|
|
913
|
+
return joinApiUrl(baseUrl, suffix);
|
|
914
|
+
}
|
|
915
|
+
|
|
426
916
|
function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
427
917
|
const {
|
|
428
918
|
BUILTIN_CLAUDE_PROXY_SETTINGS_FILE,
|
|
@@ -433,7 +923,9 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
433
923
|
HTTPS_KEEP_ALIVE_AGENT,
|
|
434
924
|
readConfigOrVirtualDefault,
|
|
435
925
|
resolveBuiltinProxyProviderName,
|
|
436
|
-
resolveAuthTokenFromCurrentProfile
|
|
926
|
+
resolveAuthTokenFromCurrentProfile,
|
|
927
|
+
OPENAI_BRIDGE_SETTINGS_FILE,
|
|
928
|
+
resolveOpenaiBridgeUpstream
|
|
437
929
|
} = deps;
|
|
438
930
|
|
|
439
931
|
if (!BUILTIN_CLAUDE_PROXY_SETTINGS_FILE) {
|
|
@@ -462,18 +954,32 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
462
954
|
const host = typeof merged.host === 'string' ? merged.host.trim() : '';
|
|
463
955
|
const port = parseInt(String(merged.port), 10);
|
|
464
956
|
const provider = typeof merged.provider === 'string' ? merged.provider.trim() : '';
|
|
957
|
+
const upstreamProviderName = typeof merged.upstreamProviderName === 'string' ? merged.upstreamProviderName.trim() : '';
|
|
958
|
+
const upstreamBaseUrl = typeof merged.upstreamBaseUrl === 'string' ? merged.upstreamBaseUrl.trim() : '';
|
|
959
|
+
const upstreamApiKey = typeof merged.upstreamApiKey === 'string' ? merged.upstreamApiKey.trim() : '';
|
|
465
960
|
const authSourceRaw = typeof merged.authSource === 'string' ? merged.authSource.trim().toLowerCase() : '';
|
|
961
|
+
const targetApiRaw = typeof merged.targetApi === 'string' ? merged.targetApi.trim().toLowerCase() : '';
|
|
466
962
|
const timeoutMs = parseInt(String(merged.timeoutMs), 10);
|
|
467
963
|
const authSource = authSourceRaw === 'profile' || authSourceRaw === 'none' || authSourceRaw === 'request'
|
|
468
964
|
? authSourceRaw
|
|
469
965
|
: 'provider';
|
|
966
|
+
let targetApi = 'responses';
|
|
967
|
+
if (targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions') {
|
|
968
|
+
targetApi = 'chat_completions';
|
|
969
|
+
} else if (targetApiRaw === 'ollama') {
|
|
970
|
+
targetApi = 'ollama';
|
|
971
|
+
}
|
|
470
972
|
|
|
471
973
|
return {
|
|
472
974
|
enabled: merged.enabled !== false,
|
|
473
975
|
host: host || DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host,
|
|
474
976
|
port: Number.isFinite(port) && port > 0 && port <= 65535 ? port : DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.port,
|
|
475
977
|
provider,
|
|
978
|
+
upstreamProviderName,
|
|
979
|
+
upstreamBaseUrl,
|
|
980
|
+
upstreamApiKey,
|
|
476
981
|
authSource,
|
|
982
|
+
targetApi,
|
|
477
983
|
timeoutMs: Number.isFinite(timeoutMs) && timeoutMs >= 1000
|
|
478
984
|
? timeoutMs
|
|
479
985
|
: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.timeoutMs
|
|
@@ -507,6 +1013,17 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
507
1013
|
...merged,
|
|
508
1014
|
provider: finalProvider
|
|
509
1015
|
};
|
|
1016
|
+
const payloadObject = isPlainObject(payload) ? payload : {};
|
|
1017
|
+
const payloadHasDirectUpstream = Object.prototype.hasOwnProperty.call(payloadObject, 'upstreamBaseUrl');
|
|
1018
|
+
const payloadSelectsProvider = Object.prototype.hasOwnProperty.call(payloadObject, 'provider')
|
|
1019
|
+
|| Object.prototype.hasOwnProperty.call(payloadObject, 'upstreamProviderName');
|
|
1020
|
+
const payloadSelectsResponses = Object.prototype.hasOwnProperty.call(payloadObject, 'targetApi')
|
|
1021
|
+
&& normalized.targetApi === 'responses';
|
|
1022
|
+
if (!payloadHasDirectUpstream && (payloadSelectsProvider || payloadSelectsResponses)) {
|
|
1023
|
+
normalized.upstreamProviderName = '';
|
|
1024
|
+
normalized.upstreamBaseUrl = '';
|
|
1025
|
+
normalized.upstreamApiKey = '';
|
|
1026
|
+
}
|
|
510
1027
|
|
|
511
1028
|
if (!options.skipWrite) {
|
|
512
1029
|
writeJsonAtomic(BUILTIN_CLAUDE_PROXY_SETTINGS_FILE, normalized);
|
|
@@ -539,9 +1056,36 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
539
1056
|
return { error: `上游 provider 不存在: ${providerName}` };
|
|
540
1057
|
}
|
|
541
1058
|
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
1059
|
+
const targetApi = settings.targetApi === 'chat_completions'
|
|
1060
|
+
? 'chat_completions'
|
|
1061
|
+
: (settings.targetApi === 'ollama' ? 'ollama' : 'responses');
|
|
1062
|
+
if (targetApi === 'responses') {
|
|
1063
|
+
const wireApi = normalizeWireApi(provider.wire_api);
|
|
1064
|
+
if (wireApi !== 'responses') {
|
|
1065
|
+
return { error: `Claude 兼容代理仅支持上游 responses provider: ${providerName}` };
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (targetApi === 'chat_completions'
|
|
1070
|
+
&& provider.codexmate_bridge === 'openai'
|
|
1071
|
+
&& typeof resolveOpenaiBridgeUpstream === 'function'
|
|
1072
|
+
&& OPENAI_BRIDGE_SETTINGS_FILE) {
|
|
1073
|
+
const bridgeUpstream = resolveOpenaiBridgeUpstream(OPENAI_BRIDGE_SETTINGS_FILE, providerName);
|
|
1074
|
+
if (!bridgeUpstream || bridgeUpstream.error) {
|
|
1075
|
+
return { error: bridgeUpstream && bridgeUpstream.error ? bridgeUpstream.error : `OpenAI bridge 配置未找到: ${providerName}` };
|
|
1076
|
+
}
|
|
1077
|
+
const bridgeBaseUrl = typeof bridgeUpstream.baseUrl === 'string' ? bridgeUpstream.baseUrl.trim() : '';
|
|
1078
|
+
if (!bridgeBaseUrl || !isValidHttpUrl(bridgeBaseUrl)) {
|
|
1079
|
+
return { error: `OpenAI 转换上游 base_url 无效: ${providerName}` };
|
|
1080
|
+
}
|
|
1081
|
+
const bridgeToken = typeof bridgeUpstream.apiKey === 'string' ? bridgeUpstream.apiKey.trim() : '';
|
|
1082
|
+
return {
|
|
1083
|
+
providerName,
|
|
1084
|
+
baseUrl: normalizeBaseUrl(bridgeBaseUrl),
|
|
1085
|
+
authHeader: bridgeToken ? (/^bearer\s+/i.test(bridgeToken) ? bridgeToken : `Bearer ${bridgeToken}`) : '',
|
|
1086
|
+
extraHeaders: isPlainObject(bridgeUpstream.headers) ? bridgeUpstream.headers : {},
|
|
1087
|
+
targetApi
|
|
1088
|
+
};
|
|
545
1089
|
}
|
|
546
1090
|
|
|
547
1091
|
const baseUrl = typeof provider.base_url === 'string' ? provider.base_url.trim() : '';
|
|
@@ -567,7 +1111,43 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
567
1111
|
return {
|
|
568
1112
|
providerName,
|
|
569
1113
|
baseUrl: normalizeBaseUrl(baseUrl),
|
|
570
|
-
authHeader
|
|
1114
|
+
authHeader,
|
|
1115
|
+
extraHeaders: {},
|
|
1116
|
+
targetApi
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function resolveBuiltinClaudeProxyDirectUpstream(settings, payload = {}) {
|
|
1121
|
+
const targetApi = settings.targetApi === 'chat_completions'
|
|
1122
|
+
? 'chat_completions'
|
|
1123
|
+
: (settings.targetApi === 'ollama' ? 'ollama' : 'responses');
|
|
1124
|
+
const baseUrl = typeof payload.upstreamBaseUrl === 'string' && payload.upstreamBaseUrl.trim()
|
|
1125
|
+
? payload.upstreamBaseUrl.trim()
|
|
1126
|
+
: (typeof settings.upstreamBaseUrl === 'string' ? settings.upstreamBaseUrl.trim() : '');
|
|
1127
|
+
if (!baseUrl) {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
if (!isValidHttpUrl(baseUrl)) {
|
|
1131
|
+
return { error: 'Claude 兼容代理上游 base_url 无效' };
|
|
1132
|
+
}
|
|
1133
|
+
const token = typeof payload.upstreamApiKey === 'string' && payload.upstreamApiKey.trim()
|
|
1134
|
+
? payload.upstreamApiKey.trim()
|
|
1135
|
+
: (typeof settings.upstreamApiKey === 'string' ? settings.upstreamApiKey.trim() : '');
|
|
1136
|
+
let authHeader = '';
|
|
1137
|
+
if (token) {
|
|
1138
|
+
authHeader = /^bearer\s+/i.test(token) ? token : `Bearer ${token}`;
|
|
1139
|
+
}
|
|
1140
|
+
const providerName = typeof payload.upstreamProviderName === 'string' && payload.upstreamProviderName.trim()
|
|
1141
|
+
? payload.upstreamProviderName.trim()
|
|
1142
|
+
: (typeof settings.upstreamProviderName === 'string' && settings.upstreamProviderName.trim()
|
|
1143
|
+
? settings.upstreamProviderName.trim()
|
|
1144
|
+
: 'claude-config');
|
|
1145
|
+
return {
|
|
1146
|
+
providerName,
|
|
1147
|
+
baseUrl: normalizeBaseUrl(baseUrl),
|
|
1148
|
+
authHeader,
|
|
1149
|
+
extraHeaders: {},
|
|
1150
|
+
targetApi
|
|
571
1151
|
};
|
|
572
1152
|
}
|
|
573
1153
|
|
|
@@ -656,7 +1236,7 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
656
1236
|
|
|
657
1237
|
function requestBuiltinClaudeProxyUpstream(upstream, requestOptions = {}) {
|
|
658
1238
|
const pathSuffix = typeof requestOptions.pathSuffix === 'string' ? requestOptions.pathSuffix : '';
|
|
659
|
-
const targetBase =
|
|
1239
|
+
const targetBase = joinBuiltinClaudeProxyUpstreamUrl(upstream.baseUrl, pathSuffix);
|
|
660
1240
|
if (!targetBase) {
|
|
661
1241
|
return Promise.reject(new Error('failed to build upstream URL'));
|
|
662
1242
|
}
|
|
@@ -770,7 +1350,7 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
770
1350
|
ok: true,
|
|
771
1351
|
upstreamProvider: upstream.providerName,
|
|
772
1352
|
upstreamBaseUrl: upstream.baseUrl,
|
|
773
|
-
mode: 'anthropic-to-responses'
|
|
1353
|
+
mode: upstream.targetApi === 'chat_completions' ? 'anthropic-to-chat-completions' : (upstream.targetApi === 'ollama' ? 'anthropic-to-ollama' : 'anthropic-to-responses')
|
|
774
1354
|
});
|
|
775
1355
|
res.writeHead(200, {
|
|
776
1356
|
'Content-Type': 'application/json; charset=utf-8',
|
|
@@ -794,8 +1374,9 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
794
1374
|
}
|
|
795
1375
|
const upstreamResponse = await requestBuiltinClaudeProxyUpstream(upstream, {
|
|
796
1376
|
method: 'GET',
|
|
797
|
-
pathSuffix: 'models',
|
|
1377
|
+
pathSuffix: upstream.targetApi === 'ollama' ? 'api/tags' : 'models',
|
|
798
1378
|
authHeader: authResult.authHeader,
|
|
1379
|
+
headers: upstream.extraHeaders,
|
|
799
1380
|
timeoutMs: settings.timeoutMs
|
|
800
1381
|
});
|
|
801
1382
|
if (upstreamResponse.statusCode < 200 || upstreamResponse.statusCode >= 300) {
|
|
@@ -828,12 +1409,20 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
828
1409
|
}
|
|
829
1410
|
|
|
830
1411
|
const payload = await readJsonRequestBody(req);
|
|
831
|
-
const
|
|
1412
|
+
const activeTargetApi = upstream.targetApi === 'ollama' || settings.targetApi === 'ollama'
|
|
1413
|
+
? 'ollama'
|
|
1414
|
+
: (upstream.targetApi === 'chat_completions' || settings.targetApi === 'chat_completions' ? 'chat_completions' : 'responses');
|
|
1415
|
+
const upstreamRequestBody = activeTargetApi === 'ollama'
|
|
1416
|
+
? buildBuiltinClaudeOllamaChatRequest(payload)
|
|
1417
|
+
: (activeTargetApi === 'chat_completions'
|
|
1418
|
+
? buildBuiltinClaudeChatCompletionsRequest(payload)
|
|
1419
|
+
: buildBuiltinClaudeResponsesRequest(payload));
|
|
832
1420
|
const upstreamResponse = await requestBuiltinClaudeProxyUpstream(upstream, {
|
|
833
1421
|
method: 'POST',
|
|
834
|
-
pathSuffix: 'responses',
|
|
1422
|
+
pathSuffix: activeTargetApi === 'ollama' ? 'api/chat' : (activeTargetApi === 'chat_completions' ? 'chat/completions' : 'responses'),
|
|
835
1423
|
body: upstreamRequestBody,
|
|
836
1424
|
authHeader: authResult.authHeader,
|
|
1425
|
+
headers: upstream.extraHeaders,
|
|
837
1426
|
timeoutMs: settings.timeoutMs
|
|
838
1427
|
});
|
|
839
1428
|
|
|
@@ -847,7 +1436,11 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
847
1436
|
return;
|
|
848
1437
|
}
|
|
849
1438
|
|
|
850
|
-
const anthropicMessage =
|
|
1439
|
+
const anthropicMessage = activeTargetApi === 'ollama'
|
|
1440
|
+
? buildAnthropicMessageFromOllamaChat(upstreamResponse.payload || {}, payload)
|
|
1441
|
+
: (activeTargetApi === 'chat_completions'
|
|
1442
|
+
? buildAnthropicMessageFromChatCompletion(upstreamResponse.payload || {}, payload)
|
|
1443
|
+
: buildAnthropicMessageFromResponses(upstreamResponse.payload || {}, payload));
|
|
851
1444
|
if (payload.stream === true) {
|
|
852
1445
|
writeAnthropicStreamEvents(res, anthropicMessage);
|
|
853
1446
|
return;
|
|
@@ -934,7 +1527,7 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
934
1527
|
return { error: saveResult.error };
|
|
935
1528
|
}
|
|
936
1529
|
const settings = saveResult.settings;
|
|
937
|
-
const upstream = resolveBuiltinClaudeProxyUpstream(settings);
|
|
1530
|
+
const upstream = resolveBuiltinClaudeProxyDirectUpstream(settings, payload) || resolveBuiltinClaudeProxyUpstream(settings);
|
|
938
1531
|
if (upstream.error) {
|
|
939
1532
|
return { error: upstream.error };
|
|
940
1533
|
}
|
|
@@ -946,7 +1539,7 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
946
1539
|
running: true,
|
|
947
1540
|
listenUrl: runtime.listenUrl,
|
|
948
1541
|
upstreamProvider: upstream.providerName,
|
|
949
|
-
mode: 'anthropic-to-responses',
|
|
1542
|
+
mode: upstream.targetApi === 'chat_completions' ? 'anthropic-to-chat-completions' : (upstream.targetApi === 'ollama' ? 'anthropic-to-ollama' : 'anthropic-to-responses'),
|
|
950
1543
|
settings
|
|
951
1544
|
};
|
|
952
1545
|
} catch (e) {
|
|
@@ -995,7 +1588,7 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
995
1588
|
listenUrl: runtime.listenUrl,
|
|
996
1589
|
upstreamProvider: runtime.upstream.providerName,
|
|
997
1590
|
upstreamBaseUrl: runtime.upstream.baseUrl,
|
|
998
|
-
mode: 'anthropic-to-responses'
|
|
1591
|
+
mode: runtime.upstream.targetApi === 'chat_completions' ? 'anthropic-to-chat-completions' : (runtime.upstream.targetApi === 'ollama' ? 'anthropic-to-ollama' : 'anthropic-to-responses')
|
|
999
1592
|
}
|
|
1000
1593
|
: null
|
|
1001
1594
|
};
|
|
@@ -1016,7 +1609,11 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
|
|
|
1016
1609
|
module.exports = {
|
|
1017
1610
|
createBuiltinClaudeProxyRuntimeController,
|
|
1018
1611
|
buildBuiltinClaudeResponsesRequest,
|
|
1612
|
+
buildBuiltinClaudeChatCompletionsRequest,
|
|
1613
|
+
buildBuiltinClaudeOllamaChatRequest,
|
|
1019
1614
|
buildAnthropicMessageFromResponses,
|
|
1615
|
+
buildAnthropicMessageFromChatCompletion,
|
|
1616
|
+
buildAnthropicMessageFromOllamaChat,
|
|
1020
1617
|
buildAnthropicStreamEvents,
|
|
1021
1618
|
buildAnthropicModelsPayload
|
|
1022
1619
|
};
|