route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
417 lines • 14.6 kB
JavaScript
;
/**
* CodeWhisperer Transformer - 统一格式转换
* 将CodeWhisperer格式与统一格式之间进行转换
* 项目所有者: Jason Zhang
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CodeWhispererTransformer = void 0;
exports.createCodeWhispererTransformer = createCodeWhispererTransformer;
const logger_1 = require("@/utils/logger");
class CodeWhispererTransformer {
name = 'codewhisperer';
/**
* 🎯 Convert BaseRequest (Anthropic format) to CodeWhisperer format
* 这是Provider调用的主要入口点
*/
transformBaseToCodeWhisperer(request) {
if (!request) {
throw new Error('BaseRequest is null or undefined - violates zero fallback principle');
}
const cwRequest = {
model: request.model,
max_tokens: request.max_tokens || 131072,
temperature: request.temperature,
messages: this.convertAnthropicMessagesToCodeWhisperer(request.messages || [])
};
// 处理系统消息
if (request.system) {
cwRequest.system = typeof request.system === 'string'
? [{ type: 'text', text: request.system }]
: request.system;
}
// 处理工具定义
if (request.tools && Array.isArray(request.tools) && request.tools.length > 0) {
cwRequest.tools = this.convertAnthropicToolsToCodeWhisperer(request.tools);
}
console.log('🔄 [CODEWHISPERER-TRANSFORMER] BaseRequest -> CodeWhisperer:', {
hasTools: !!(cwRequest.tools && cwRequest.tools.length > 0),
toolCount: cwRequest.tools?.length || 0,
messageCount: cwRequest.messages.length,
model: cwRequest.model
});
return cwRequest;
}
/**
* 🎯 Convert CodeWhisperer response to BaseResponse (Anthropic format)
* 这是Provider调用的主要出口点
*/
transformCodeWhispererToBase(response, originalRequest) {
if (!response) {
throw new Error('CodeWhisperer response is null or undefined - silent failure detected');
}
if (!response.content || response.content.length === 0) {
throw new Error('CodeWhisperer response missing content - invalid response format');
}
// 🎯 修复finish_reason映射
const finishReason = this.mapCodeWhispererStopReasonToAnthropic(response.stop_reason, this.hasToolCallsInContent(response.content));
const baseResponse = {
id: response.id || `msg_${Date.now()}`,
content: response.content, // CodeWhisperer already uses Anthropic content format
model: originalRequest.metadata?.originalModel || response.model,
role: 'assistant',
stop_reason: finishReason,
stop_sequence: null,
usage: {
input_tokens: response.usage.input_tokens || 0,
output_tokens: response.usage.output_tokens || 0
}
};
console.log('🔄 [CODEWHISPERER-TRANSFORMER] CodeWhisperer -> BaseResponse:', {
hasTools: response.content.some((c) => c.type === 'tool_use'),
toolCount: response.content.filter((c) => c.type === 'tool_use').length,
stopReason: finishReason,
contentBlocks: response.content.length
});
return baseResponse;
}
/**
* 转换请求到统一格式
*/
transformRequestToUnified(request) {
const unified = {
model: request.model,
messages: this.normalizeMessages(request.messages),
max_tokens: request.max_tokens || 131072,
temperature: request.temperature,
stream: request.stream || false
};
// 处理system消息
if (request.system && Array.isArray(request.system)) {
unified.system = request.system.map(s => s.text).join('\n\n');
}
// 处理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
}
}));
}
logger_1.logger.debug('CodeWhisperer request transformed to unified', {
model: unified.model,
messageCount: unified.messages.length,
hasSystem: !!unified.system,
hasTools: !!(unified.tools && unified.tools.length > 0)
});
return unified;
}
/**
* 从统一格式转换请求
*/
transformRequestFromUnified(unified) {
const request = {
model: unified.model,
max_tokens: unified.max_tokens || 131072,
messages: this.denormalizeMessages(unified.messages),
stream: unified.stream || false,
temperature: unified.temperature
};
// 处理system消息
if (unified.system) {
request.system = [{ type: 'text', text: unified.system }];
}
// 处理tools
if (unified.tools && unified.tools.length > 0) {
request.tools = unified.tools.map(tool => ({
name: tool.function.name,
description: tool.function.description,
input_schema: tool.function.parameters
}));
}
logger_1.logger.debug('Unified request transformed to CodeWhisperer', {
model: request.model,
messageCount: request.messages.length,
hasSystem: !!(request.system && request.system.length > 0),
hasTools: !!(request.tools && request.tools.length > 0)
});
return request;
}
/**
* 转换响应到统一格式
*/
transformResponseToUnified(response) {
const unified = {
id: response.id,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: response.model,
choices: [{
index: 0,
message: {
role: response.role,
content: this.extractTextContent(response.content),
tool_calls: this.extractToolCalls(response.content)
},
finish_reason: this.mapStopReason(response.stop_reason)
}],
usage: {
prompt_tokens: response.usage.input_tokens,
completion_tokens: response.usage.output_tokens,
total_tokens: response.usage.input_tokens + response.usage.output_tokens
}
};
logger_1.logger.debug('CodeWhisperer response transformed to unified', {
id: unified.id,
contentBlocks: response.content.length,
stopReason: unified.choices[0].finish_reason
});
return unified;
}
/**
* 从统一格式转换响应
*/
transformResponseFromUnified(unified) {
const choice = unified.choices[0];
const response = {
id: unified.id,
type: 'message',
model: unified.model,
role: choice.message.role,
content: this.buildContentFromUnified(choice.message),
stop_reason: this.mapStopReason(choice.finish_reason),
stop_sequence: null,
usage: {
input_tokens: unified.usage.prompt_tokens,
output_tokens: unified.usage.completion_tokens
}
};
logger_1.logger.debug('Unified response transformed to CodeWhisperer', {
id: response.id,
contentBlocks: response.content.length,
stopReason: response.stop_reason
});
return response;
}
/**
* 标准化消息格式
*/
normalizeMessages(messages) {
return messages.map(message => ({
role: message.role,
content: this.normalizeMessageContent(message.content)
}));
}
/**
* 反标准化消息格式
*/
denormalizeMessages(messages) {
return messages.map(message => ({
role: message.role,
content: this.denormalizeMessageContent(message.content)
}));
}
/**
* 标准化消息内容
*/
normalizeMessageContent(content) {
if (typeof content === 'string') {
return content;
}
if (Array.isArray(content)) {
return content.map(block => {
if (block.type === 'text') {
return { type: 'text', text: block.text };
}
if (block.type === 'tool_use') {
return {
type: 'tool_use',
id: block.id,
name: block.name,
input: block.input
};
}
return block;
});
}
return content;
}
/**
* 反标准化消息内容
*/
denormalizeMessageContent(content) {
// CodeWhisperer使用与Anthropic相似的格式,直接返回
return content;
}
/**
* 标准化内容块
*/
normalizeContent(content) {
return content.map(block => {
if (block.type === 'text') {
return { type: 'text', text: block.text };
}
if (block.type === 'tool_use') {
return {
type: 'tool_use',
id: block.id,
name: block.name,
input: block.input
};
}
return block;
});
}
/**
* 反标准化内容块
*/
denormalizeContent(content) {
return content.map(block => {
if (block.type === 'text') {
return { type: 'text', text: block.text };
}
if (block.type === 'tool_use') {
return {
type: 'tool_use',
id: block.id,
name: block.name,
input: block.input
};
}
return block;
});
}
/**
* 映射stop reason到统一格式
*/
mapStopReason(stopReason) {
const mapping = {
'end_turn': 'end_turn',
'max_tokens': 'max_tokens',
'tool_use': 'tool_use',
'stop_sequence': 'stop_sequence'
};
return mapping[stopReason] || stopReason;
}
/**
* 🔧 Convert Anthropic messages to CodeWhisperer format
*/
convertAnthropicMessagesToCodeWhisperer(messages) {
if (!Array.isArray(messages)) {
throw new Error('Messages must be an array - violates zero fallback principle');
}
return messages.map(msg => {
if (!msg || typeof msg !== 'object') {
throw new Error('Invalid message object - violates zero fallback principle');
}
const cwMsg = {
role: msg.role,
content: msg.content
};
return cwMsg;
});
}
/**
* 🔧 Convert Anthropic tools to CodeWhisperer format
*/
convertAnthropicToolsToCodeWhisperer(tools) {
if (!Array.isArray(tools)) {
throw new Error('Tools must be an array - violates zero fallback principle');
}
return tools.map(tool => {
if (!tool || typeof tool !== 'object') {
throw new Error('Invalid tool object - violates zero fallback principle');
}
if (!tool.name) {
throw new Error('Tool missing name - violates zero fallback principle');
}
return {
name: tool.name,
description: tool.description || '',
input_schema: tool.input_schema || {}
};
});
}
/**
* 🎯 Map CodeWhisperer stop_reason to Anthropic stop_reason
*/
mapCodeWhispererStopReasonToAnthropic(stopReason, hasToolCalls) {
// 🔧 Critical Fix: 如果有工具调用,强制返回tool_use
if (hasToolCalls && (stopReason === 'end_turn' || stopReason === 'stop')) {
return 'tool_use';
}
const mapping = {
'stop': 'end_turn',
'end_turn': 'end_turn',
'max_tokens': 'max_tokens',
'tool_use': 'tool_use',
'stop_sequence': 'stop_sequence'
};
return mapping[stopReason] || 'end_turn';
}
/**
* 🔧 Check if content has tool calls
*/
hasToolCallsInContent(content) {
return content.some((block) => block.type === 'tool_use');
}
/**
* 提取文本内容
*/
extractTextContent(content) {
const textBlocks = content.filter(block => block.type === 'text');
if (textBlocks.length === 0)
return null;
return textBlocks.map(block => block.text).join('');
}
/**
* 提取工具调用
*/
extractToolCalls(content) {
const toolCalls = content.filter(block => block.type === 'tool_use');
if (toolCalls.length === 0)
return undefined;
return toolCalls.map(tool => ({
id: tool.id,
type: 'function',
function: {
name: tool.name,
arguments: JSON.stringify(tool.input)
}
}));
}
/**
* 从统一格式构建内容
*/
buildContentFromUnified(message) {
const content = [];
if (message.content) {
content.push({
type: 'text',
text: message.content
});
}
if (message.tool_calls) {
for (const toolCall of message.tool_calls) {
content.push({
type: 'tool_use',
id: toolCall.id,
name: toolCall.function.name,
input: JSON.parse(toolCall.function.arguments)
});
}
}
return content;
}
}
exports.CodeWhispererTransformer = CodeWhispererTransformer;
/**
* 创建CodeWhisperer转换器实例
*/
function createCodeWhispererTransformer() {
return new CodeWhispererTransformer();
}
//# sourceMappingURL=codewhisperer.js.map