feat: 添加响应内容伪装替换功能
- 将 Droid 替换为 Claude - 将 Factory 替换为 Anthropic - 支持流式和非流式响应 - 让用户感知为原生 Claude 服务
This commit is contained in:
107
routes.js
107
routes.js
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user