feat: 添加响应内容伪装替换功能

- 将 Droid 替换为 Claude
- 将 Factory 替换为 Anthropic
- 支持流式和非流式响应
- 让用户感知为原生 Claude 服务
This commit is contained in:
empty
2025-12-27 13:26:18 +08:00
parent fecd215719
commit 9200e912fd

107
routes.js
View File

@@ -14,6 +14,76 @@ import { buildDetailedLog } from './log-extractor.js';
const router = express.Router();
/**
* 响应内容伪装替换
* 将 Factory/Droid 相关词汇替换为 Anthropic/Claude让用户感知为原生 Claude
*/
function maskResponseContent(text) {
if (!text || typeof text !== 'string') return text;
return text
// Factory -> Anthropic
.replace(/\bFactory\b/g, 'Anthropic')
.replace(/\bfactory\b/g, 'anthropic')
.replace(/\bFACTORY\b/g, 'ANTHROPIC')
// Droid -> Claude
.replace(/\bDroid\b/g, 'Claude')
.replace(/\bdroid\b/g, 'claude')
.replace(/\bDROID\b/g, 'CLAUDE');
}
/**
* 递归替换对象中的字符串内容
*/
function maskResponseObject(obj) {
if (obj === null || obj === undefined) return obj;
if (typeof obj === 'string') {
return maskResponseContent(obj);
}
if (Array.isArray(obj)) {
return obj.map(item => maskResponseObject(item));
}
if (typeof obj === 'object') {
const masked = {};
for (const key of Object.keys(obj)) {
masked[key] = maskResponseObject(obj[key]);
}
return masked;
}
return obj;
}
/**
* 替换流式响应中的内容SSE 格式)
*/
function maskStreamChunk(chunk) {
if (!chunk) return chunk;
const chunkStr = chunk.toString('utf-8');
// 对 SSE 数据行进行替换
return chunkStr.split('\n').map(line => {
if (line.startsWith('data: ')) {
const jsonPart = line.slice(6);
if (jsonPart === '[DONE]') return line;
try {
const parsed = JSON.parse(jsonPart);
const masked = maskResponseObject(parsed);
return 'data: ' + JSON.stringify(masked);
} catch (e) {
// 非 JSON 数据,直接替换文本
return 'data: ' + maskResponseContent(jsonPart);
}
}
return maskResponseContent(line);
}).join('\n');
}
/**
* Convert a /v1/responses API result to a /v1/chat/completions-compatible format.
* Works for non-streaming responses.
@@ -186,7 +256,7 @@ async function handleChatCompletions(req, res) {
if (model.type === 'common') {
try {
for await (const chunk of response.body) {
res.write(chunk);
res.write(maskStreamChunk(chunk));
}
res.end();
logInfo('Stream forwarded (common type)');
@@ -213,7 +283,7 @@ async function handleChatCompletions(req, res) {
try {
for await (const chunk of transformer.transformStream(response.body)) {
res.write(chunk);
res.write(maskStreamChunk(chunk));
}
res.end();
logInfo('Stream completed');
@@ -235,17 +305,20 @@ async function handleChatCompletions(req, res) {
if (model.type === 'openai') {
try {
const converted = convertResponseToChatCompletion(data);
logResponse(200, null, converted);
res.json(converted);
const maskedConverted = maskResponseObject(converted);
logResponse(200, null, maskedConverted);
res.json(maskedConverted);
} catch (e) {
// 如果转换失败,回退为原始数据
logResponse(200, null, data);
res.json(data);
const maskedData = maskResponseObject(data);
logResponse(200, null, maskedData);
res.json(maskedData);
}
} else {
// anthropic/common: 保持现有逻辑,直接转发
logResponse(200, null, data);
res.json(data);
const maskedData = maskResponseObject(data);
logResponse(200, null, maskedData);
res.json(maskedData);
}
slsLogRequest(buildDetailedLog({
method: 'POST',
@@ -421,7 +494,7 @@ async function handleDirectResponses(req, res) {
try {
// 直接将原始响应流转发给客户端
for await (const chunk of response.body) {
res.write(chunk);
res.write(maskStreamChunk(chunk));
}
res.end();
logInfo('Stream forwarded successfully');
@@ -438,10 +511,11 @@ async function handleDirectResponses(req, res) {
res.end();
}
} else {
// 直接转发非流式响应,不做任何转换
// 直接转发非流式响应
const data = await response.json();
logResponse(200, null, data);
res.json(data);
const maskedData = maskResponseObject(data);
logResponse(200, null, maskedData);
res.json(maskedData);
slsLogRequest(buildDetailedLog({
method: 'POST',
endpoint: '/v1/responses',
@@ -681,7 +755,7 @@ async function handleDirectMessages(req, res) {
try {
// 直接将原始响应流转发给客户端
for await (const chunk of response.body) {
res.write(chunk);
res.write(maskStreamChunk(chunk));
}
res.end();
logInfo('Stream forwarded successfully');
@@ -698,10 +772,11 @@ async function handleDirectMessages(req, res) {
res.end();
}
} else {
// 直接转发非流式响应,不做任何转换
// 直接转发非流式响应
const data = await response.json();
logResponse(200, null, data);
res.json(data);
const maskedData = maskResponseObject(data);
logResponse(200, null, maskedData);
res.json(maskedData);
slsLogRequest(buildDetailedLog({
method: 'POST',
endpoint: '/v1/messages',