route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
422 lines • 15.7 kB
JavaScript
;
/**
* Anthropic Format Transformer
* Handles conversion between Anthropic API format and unified format
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnthropicTransformer = void 0;
exports.createAnthropicTransformer = createAnthropicTransformer;
class AnthropicTransformer {
name = 'anthropic';
/**
* Convert Anthropic request to unified format
*/
transformRequestToUnified(request) {
const unified = {
messages: this.convertMessagesToUnified(request.messages || []),
model: request.model,
max_tokens: request.max_tokens || 131072, // 128K tokens default
temperature: request.temperature,
stream: request.stream || false
};
// Handle system messages
if (request.system) {
if (typeof request.system === 'string') {
unified.system = request.system;
}
else if (Array.isArray(request.system)) {
unified.system = request.system
.filter((s) => s.type === 'text')
.map((s) => s.text)
.join('\n');
}
}
// Handle tools
if (request.tools && Array.isArray(request.tools)) {
unified.tools = request.tools.map((tool) => ({
type: 'function',
function: {
name: tool.name,
description: tool.description || '',
parameters: tool.input_schema
}
}));
}
// Handle tool choice
if (request.tool_choice) {
if (request.tool_choice.type === 'auto') {
unified.tool_choice = 'auto';
}
else if (request.tool_choice.type === 'tool' && request.tool_choice.name) {
unified.tool_choice = request.tool_choice.name;
}
}
return unified;
}
/**
* Convert unified request to Anthropic format
*/
transformRequestFromUnified(request) {
const anthropicRequest = {
model: request.model,
messages: this.convertMessagesFromUnified(request.messages),
max_tokens: request.max_tokens || 131072, // 128K tokens default
temperature: request.temperature,
stream: request.stream || false
};
// Handle system messages
if (request.system) {
anthropicRequest.system = request.system;
}
// Handle tools
if (request.tools && request.tools.length > 0) {
anthropicRequest.tools = request.tools.map(tool => ({
name: tool.function.name,
description: tool.function.description,
input_schema: tool.function.parameters
}));
// Handle tool choice
if (request.tool_choice) {
if (request.tool_choice === 'auto') {
anthropicRequest.tool_choice = { type: 'auto' };
}
else if (typeof request.tool_choice === 'string') {
anthropicRequest.tool_choice = {
type: 'tool',
name: request.tool_choice
};
}
}
}
return anthropicRequest;
}
/**
* Convert Anthropic response to unified format
*/
transformResponseToUnified(response) {
const textContent = response.content?.find((c) => c.type === 'text');
const toolUses = response.content?.filter((c) => c.type === 'tool_use') || [];
const toolCalls = toolUses.map((toolUse, index) => ({
id: toolUse.id || `call_${Date.now()}_${index}`,
type: 'function',
function: {
name: toolUse.name,
arguments: JSON.stringify(toolUse.input || {})
}
}));
// 直接映射stop_reason为finish_reason,保持原始性
let finishReason = undefined;
if (response.stop_reason) {
const stopReasonMapping = {
'end_turn': 'stop',
'max_tokens': 'length',
'tool_use': 'tool_calls',
'stop_sequence': 'stop'
};
finishReason = stopReasonMapping[response.stop_reason] || response.stop_reason;
}
return {
id: response.id || `chatcmpl-${Date.now()}`,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: response.model,
choices: [{
index: 0,
message: {
role: 'assistant',
content: textContent?.text || null,
tool_calls: toolCalls.length > 0 ? toolCalls : undefined
},
// 只有在有原始finish_reason时才设置,否则为undefined
finish_reason: finishReason
}],
usage: {
prompt_tokens: response.usage?.input_tokens || 0,
completion_tokens: response.usage?.output_tokens || 0,
total_tokens: (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0)
}
};
}
/**
* Convert unified response to Anthropic format
*/
transformResponseFromUnified(response) {
const choice = response.choices[0];
const content = [];
// Add text content
if (choice.message.content) {
content.push({
type: 'text',
text: choice.message.content
});
}
// Add tool uses
if (choice.message.tool_calls) {
choice.message.tool_calls.forEach(toolCall => {
content.push({
type: 'tool_use',
id: toolCall.id,
name: toolCall.function.name,
input: JSON.parse(toolCall.function.arguments || '{}')
});
});
}
// 直接映射finish_reason为stop_reason,不使用外部处理器
let stopReason = undefined;
if (choice.finish_reason) {
const finishReasonMapping = {
'stop': 'end_turn',
'length': 'max_tokens',
'tool_calls': 'tool_use',
'function_call': 'tool_use',
'content_filter': 'stop_sequence'
};
stopReason = finishReasonMapping[choice.finish_reason] || choice.finish_reason;
}
const result = {
id: response.id,
type: 'message',
role: 'assistant',
content,
model: response.model,
stop_sequence: null,
usage: {
input_tokens: response.usage.prompt_tokens,
output_tokens: response.usage.completion_tokens
}
};
// 只有在有映射结果时才设置stop_reason
if (stopReason) {
result.stop_reason = stopReason;
}
return result;
}
/**
* Convert Anthropic streaming chunk to unified format
*/
transformStreamChunk(chunk) {
if (chunk.type === 'content_block_delta' && chunk.delta?.text) {
return {
id: `chatcmpl-${Date.now()}`,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: 'unknown',
choices: [{
index: 0,
delta: {
content: chunk.delta.text
}
}]
};
}
if (chunk.type === 'message_delta' && chunk.delta?.stop_reason) {
// 保留stop_reason,传递给下游处理
return {
id: `chatcmpl-${Date.now()}`,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: 'unknown',
choices: [{
index: 0,
delta: {},
finish_reason: chunk.delta.stop_reason // 保留finish_reason传递给下游
}]
};
}
return null;
}
/**
* Convert messages to unified format
*/
convertMessagesToUnified(messages) {
return messages.map(msg => {
const unified = {
role: msg.role,
content: this.convertContentToUnified(msg.content)
};
return unified;
});
}
/**
* Convert messages from unified format
*/
convertMessagesFromUnified(messages) {
const anthropicMessages = [];
for (let i = 0; i < messages.length; i++) {
const msg = messages[i];
if (msg.role === 'system') {
continue; // System messages handled separately
}
const anthropicMsg = {
role: msg.role,
content: this.convertContentFromUnified(msg, messages, i)
};
anthropicMessages.push(anthropicMsg);
}
return this.preprocessMessages(anthropicMessages);
}
/**
* Convert content to unified format
*/
convertContentToUnified(content) {
if (typeof content === 'string') {
return content;
}
if (Array.isArray(content)) {
return content.map(block => {
if (block.type === 'text') {
return block.text;
}
else if (block.type === 'tool_result') {
const result = typeof block.content === 'string'
? block.content
: JSON.stringify(block.content);
return `Tool result (${block.tool_use_id}): ${result}`;
}
else if (block.type === 'tool_use') {
return `Tool call: ${block.name}(${JSON.stringify(block.input)})`;
}
return JSON.stringify(block);
}).join('\n');
}
return JSON.stringify(content);
}
/**
* Convert content from unified format
*/
convertContentFromUnified(msg, allMessages, index) {
const content = [];
// Handle tool results for user messages
if (msg.role === 'user') {
// Look for preceding tool messages
const toolResults = [];
let j = index - 1;
while (j >= 0 && allMessages[j].role === 'tool') {
toolResults.unshift(allMessages[j]);
j--;
}
// Add tool results first
toolResults.forEach(toolMsg => {
content.push({
type: 'tool_result',
tool_use_id: toolMsg.tool_call_id,
content: toolMsg.content || ''
});
});
// Add user text content
if (msg.content && typeof msg.content === 'string' && msg.content.trim()) {
content.push({
type: 'text',
text: msg.content
});
}
return content.length === 1 && content[0].type === 'text'
? content[0].text
: content.length === 0
? ''
: content;
}
// Handle assistant messages with tool calls
if (msg.role === 'assistant') {
// Add text content
if (msg.content && typeof msg.content === 'string' && msg.content.trim()) {
content.push({
type: 'text',
text: msg.content
});
}
// Add tool uses
if (msg.tool_calls) {
msg.tool_calls.forEach(toolCall => {
content.push({
type: 'tool_use',
id: toolCall.id,
name: toolCall.function.name,
input: JSON.parse(toolCall.function.arguments || '{}')
});
});
}
return content.length === 1 && content[0].type === 'text'
? content[0].text
: content;
}
return msg.content || '';
}
/**
* Preprocess messages to handle tool result placement
*/
preprocessMessages(messages) {
const processed = [];
const toolResultMap = new Map();
const toolResultPositions = new Map();
// First pass: collect tool results and their positions
messages.forEach((msg, index) => {
if (Array.isArray(msg.content)) {
msg.content.forEach((block) => {
if (block.type === 'tool_result' && block.tool_use_id) {
toolResultMap.set(block.tool_use_id, block);
toolResultPositions.set(block.tool_use_id, index);
}
});
}
});
// Second pass: process messages and handle tool result placement
for (let i = 0; i < messages.length; i++) {
const msg = messages[i];
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
const toolUses = msg.content.filter((block) => block.type === 'tool_use');
if (toolUses.length > 0) {
processed.push(msg);
// Check if tool results are properly placed
const missingResults = [];
toolUses.forEach((toolUse) => {
if (!toolResultMap.has(toolUse.id)) {
missingResults.push({
type: 'tool_result',
tool_use_id: toolUse.id,
content: JSON.stringify({
success: true,
message: 'Tool call executed successfully'
})
});
}
});
// Add missing tool results as a user message
if (missingResults.length > 0) {
processed.push({
role: 'user',
content: missingResults
});
}
}
else {
processed.push(msg);
}
}
else {
// Filter out tool results that are already handled
if (Array.isArray(msg.content)) {
const filteredContent = msg.content.filter((block) => block.type !== 'tool_result' || !toolResultMap.has(block.tool_use_id));
if (filteredContent.length > 0) {
processed.push({
...msg,
content: filteredContent
});
}
}
else {
processed.push(msg);
}
}
}
return processed;
}
}
exports.AnthropicTransformer = AnthropicTransformer;
/**
* Utility function to create Anthropic transformer
*/
function createAnthropicTransformer() {
return new AnthropicTransformer();
}
//# sourceMappingURL=anthropic.js.map