Files
droid2api/transformers/request-anthropic.js
1e0n 6dca025e96 Initial commit: OpenAI compatible API proxy with auto token refresh
- Implemented OpenAI compatible API proxy server
- Support for Anthropic and custom OpenAI format conversion
- Automatic API key refresh with WorkOS OAuth
- SSE streaming response transformation
- Smart header management for Factory endpoints
- Chinese documentation
2025-10-06 02:12:01 +08:00

173 lines
5.1 KiB
JavaScript

import { logDebug } from '../logger.js';
export function transformToAnthropic(openaiRequest) {
logDebug('Transforming OpenAI request to Anthropic format');
const anthropicRequest = {
model: openaiRequest.model,
messages: [],
stream: openaiRequest.stream !== false
};
// Handle max_tokens
if (openaiRequest.max_tokens) {
anthropicRequest.max_tokens = openaiRequest.max_tokens;
} else if (openaiRequest.max_completion_tokens) {
anthropicRequest.max_tokens = openaiRequest.max_completion_tokens;
} else {
anthropicRequest.max_tokens = 4096;
}
// Extract system message(s) and transform other messages
let systemContent = [];
if (openaiRequest.messages && Array.isArray(openaiRequest.messages)) {
for (const msg of openaiRequest.messages) {
// Handle system messages separately
if (msg.role === 'system') {
if (typeof msg.content === 'string') {
systemContent.push({
type: 'text',
text: msg.content
});
} else if (Array.isArray(msg.content)) {
for (const part of msg.content) {
if (part.type === 'text') {
systemContent.push({
type: 'text',
text: part.text
});
} else {
systemContent.push(part);
}
}
}
continue; // Skip adding system messages to messages array
}
const anthropicMsg = {
role: msg.role,
content: []
};
if (typeof msg.content === 'string') {
anthropicMsg.content.push({
type: 'text',
text: msg.content
});
} else if (Array.isArray(msg.content)) {
for (const part of msg.content) {
if (part.type === 'text') {
anthropicMsg.content.push({
type: 'text',
text: part.text
});
} else if (part.type === 'image_url') {
anthropicMsg.content.push({
type: 'image',
source: part.image_url
});
} else {
anthropicMsg.content.push(part);
}
}
}
anthropicRequest.messages.push(anthropicMsg);
}
}
// Add system parameter if system content exists
if (systemContent.length > 0) {
anthropicRequest.system = systemContent;
}
// Transform tools if present
if (openaiRequest.tools && Array.isArray(openaiRequest.tools)) {
anthropicRequest.tools = openaiRequest.tools.map(tool => {
if (tool.type === 'function') {
return {
name: tool.function.name,
description: tool.function.description,
input_schema: tool.function.parameters || {}
};
}
return tool;
});
}
// Pass through other compatible parameters
if (openaiRequest.temperature !== undefined) {
anthropicRequest.temperature = openaiRequest.temperature;
}
if (openaiRequest.top_p !== undefined) {
anthropicRequest.top_p = openaiRequest.top_p;
}
if (openaiRequest.stop !== undefined) {
anthropicRequest.stop_sequences = Array.isArray(openaiRequest.stop)
? openaiRequest.stop
: [openaiRequest.stop];
}
logDebug('Transformed Anthropic request', anthropicRequest);
return anthropicRequest;
}
export function getAnthropicHeaders(authHeader, clientHeaders = {}, isStreaming = true) {
// Generate unique IDs if not provided
const sessionId = clientHeaders['x-session-id'] || generateUUID();
const messageId = clientHeaders['x-assistant-message-id'] || generateUUID();
const headers = {
'accept': 'application/json',
'content-type': 'application/json',
'anthropic-version': '2023-06-01',
'anthropic-beta': 'interleaved-thinking-2025-05-14',
'x-api-key': 'placeholder',
'authorization': authHeader || '',
'x-model-provider': 'anthropic',
'x-factory-client': 'cli',
'x-session-id': sessionId,
'x-assistant-message-id': messageId,
'user-agent': 'a$/JS 0.57.0',
'x-stainless-timeout': '600',
'connection': 'keep-alive'
};
// Pass through Stainless SDK headers with defaults
const stainlessDefaults = {
'x-stainless-arch': 'x64',
'x-stainless-lang': 'js',
'x-stainless-os': 'MacOS',
'x-stainless-runtime': 'node',
'x-stainless-retry-count': '0',
'x-stainless-package-version': '0.57.0',
'x-stainless-runtime-version': 'v24.3.0'
};
// Set helper-method based on streaming
if (isStreaming) {
headers['x-stainless-helper-method'] = 'stream';
}
// Copy Stainless headers from client or use defaults
Object.keys(stainlessDefaults).forEach(header => {
headers[header] = clientHeaders[header] || stainlessDefaults[header];
});
// Override timeout from defaults if client provided
if (clientHeaders['x-stainless-timeout']) {
headers['x-stainless-timeout'] = clientHeaders['x-stainless-timeout'];
}
return headers;
}
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}